diff --git a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/controller/RfidReadRecordController.java b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/controller/RfidReadRecordController.java index 085e048..c027fd5 100644 --- a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/controller/RfidReadRecordController.java +++ b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/controller/RfidReadRecordController.java @@ -1,11 +1,13 @@ package org.dromara.rfid.controller; +import java.util.Date; import java.util.List; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; @@ -24,6 +26,9 @@ import org.dromara.common.mybatis.core.page.TableDataInfo; /** * 读取记录 + *

+ * 支持按日期分表,表名格式:rfid_read_record_yyyyMMdd + *

* * @author zch * @date 2025-11-25 @@ -59,13 +64,15 @@ public class RfidReadRecordController extends BaseController { /** * 获取读取记录详细信息 * - * @param id 主键 + * @param id 主键 + * @param queryDate 查询日期(用于定位分表,格式:yyyy-MM-dd) */ @SaCheckPermission("rfid:rfidReadRecord:query") @GetMapping("/{id}") - public R getInfo(@NotNull(message = "主键不能为空") - @PathVariable Long id) { - return R.ok(rfidReadRecordService.queryById(id)); + public R getInfo( + @NotNull(message = "主键不能为空") @PathVariable Long id, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date queryDate) { + return R.ok(rfidReadRecordService.queryById(id, queryDate)); } /** @@ -93,14 +100,16 @@ public class RfidReadRecordController extends BaseController { /** * 删除读取记录 * - * @param ids 主键串 + * @param ids 主键串 + * @param queryDate 查询日期(用于定位分表,格式:yyyy-MM-dd) */ @SaCheckPermission("rfid:rfidReadRecord:remove") @Log(title = "读取记录", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") - public R remove(@NotEmpty(message = "主键不能为空") - @PathVariable Long[] ids) { - return toAjax(rfidReadRecordService.deleteWithValidByIds(List.of(ids), true)); + public R remove( + @NotEmpty(message = "主键不能为空") @PathVariable Long[] ids, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date queryDate) { + return toAjax(rfidReadRecordService.deleteWithValidByIds(List.of(ids), queryDate, true)); } /** diff --git a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/RfidReadRecord.java b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/RfidReadRecord.java index cd64449..ce8581a 100644 --- a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/RfidReadRecord.java +++ b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/RfidReadRecord.java @@ -10,10 +10,19 @@ import com.fasterxml.jackson.annotation.JsonFormat; import java.io.Serial; /** - * 读取记录对象 rfid_read_record + * 读取记录对象 + *

+ * 按日期分表,表名格式:rfid_read_record_yyyyMMdd + * 例如:rfid_read_record_20251126 + *

+ *

+ * 注意:此实体不使用 MyBatis-Plus 内置的 CRUD 方法, + * 所有操作通过 Mapper XML 自定义 SQL 实现,支持动态表名 + *

* * @author zch * @date 2025-11-25 + * @see org.dromara.rfid.helper.RfidReadRecordTableHelper */ @Data @EqualsAndHashCode(callSuper = true) diff --git a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/bo/RfidReadRecordBo.java b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/bo/RfidReadRecordBo.java index 4742454..f2b8bbf 100644 --- a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/bo/RfidReadRecordBo.java +++ b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/bo/RfidReadRecordBo.java @@ -12,7 +12,10 @@ import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; /** - * 读取记录业务对象 rfid_read_record + * 读取记录业务对象 + *

+ * 按日期分表,表名格式:rfid_read_record_yyyyMMdd + *

* * @author zch * @date 2025-11-25 @@ -42,16 +45,31 @@ public class RfidReadRecordBo extends BaseEntity { /** * 条码信息 + *

+ * 读取成功时必填,读取失败时可为空 + *

*/ - @NotBlank(message = "条码信息不能为空", groups = { AddGroup.class, EditGroup.class }) private String barcode; /** * 记录时间 */ @NotNull(message = "记录时间不能为空", groups = { AddGroup.class, EditGroup.class }) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date recordTime; + /** + * 记录时间范围-开始(查询用) + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date beginRecordTime; + + /** + * 记录时间范围-结束(查询用) + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endRecordTime; + /** * 是否告警(0-否;1-是) */ @@ -73,5 +91,14 @@ public class RfidReadRecordBo extends BaseEntity { */ private String alarmAction; + /** + * 采样间隔(分钟) + *

+ * 用于大数据量查询时的降采样: + * - null 或 <= 1:不采样,返回全部数据 + * - > 1:每 N 分钟取一条代表数据(取该时间段最新一条) + *

+ */ + private Integer samplingInterval; } diff --git a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/vo/RfidReadRecordVo.java b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/vo/RfidReadRecordVo.java index d08d976..1cce1fa 100644 --- a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/vo/RfidReadRecordVo.java +++ b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/domain/vo/RfidReadRecordVo.java @@ -54,6 +54,12 @@ public class RfidReadRecordVo implements Serializable { @ExcelProperty(value = "设备名称") private String deviceName; + /** + * 位置别名(连表 rfid_location.location_alias) + */ + @ExcelProperty(value = "位置") + private String locationAlias; + /** * 读取状态(1-成功;0-失败) */ @@ -71,6 +77,7 @@ public class RfidReadRecordVo implements Serializable { * 记录时间 */ @ExcelProperty(value = "记录时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date recordTime; /** diff --git a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/helper/RfidReadRecordTableHelper.java b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/helper/RfidReadRecordTableHelper.java new file mode 100644 index 0000000..594bfd3 --- /dev/null +++ b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/helper/RfidReadRecordTableHelper.java @@ -0,0 +1,244 @@ +package org.dromara.rfid.helper; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.SpringUtils; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * RFID读取记录分表工具类 + *

+ * 按日期分表,表名格式:rfid_read_record_yyyyMMdd + *

+ * + * @author zch + * @date 2025-11-26 + */ +@Slf4j +public class RfidReadRecordTableHelper { + + /** + * 基础表名 + */ + public static final String BASE_TABLE_NAME = "rfid_read_record"; + + /** + * 日期格式 + */ + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + + /** + * 表存在缓存(存在的表缓存为 true,不存在的不缓存以便下次重新检查) + *

+ * 使用 ConcurrentHashMap 保证线程安全,避免频繁查询数据库元数据 + *

+ */ + private static final Map TABLE_EXISTS_CACHE = new ConcurrentHashMap<>(64); + + /** + * 缓存刷新时间戳(用于定期刷新缓存) + */ + private static volatile long lastCacheRefreshTime = System.currentTimeMillis(); + + /** + * 缓存有效期(5分钟) + */ + private static final long CACHE_EXPIRE_MS = 5 * 60 * 1000L; + + /** + * 私有构造器 + */ + private RfidReadRecordTableHelper() { + } + + /** + * 清除表存在缓存 + *

+ * 当新建分表后可调用此方法刷新缓存 + *

+ */ + public static void clearTableExistsCache() { + TABLE_EXISTS_CACHE.clear(); + lastCacheRefreshTime = System.currentTimeMillis(); + log.debug("分表缓存已清除"); + } + + /** + * 根据日期获取表名 + * + * @param date 日期 + * @return 表名,如 rfid_read_record_20251126 + */ + public static String getTableName(Date date) { + if (date == null) { + date = new Date(); + } + String dateSuffix = DateUtil.format(date, "yyyyMMdd"); + return BASE_TABLE_NAME + "_" + dateSuffix; + } + + /** + * 根据 LocalDate 获取表名 + * + * @param localDate 日期 + * @return 表名 + */ + public static String getTableName(LocalDate localDate) { + if (localDate == null) { + localDate = LocalDate.now(); + } + return BASE_TABLE_NAME + "_" + localDate.format(DATE_FORMATTER); + } + + /** + * 获取当天的表名 + * + * @return 当天表名 + */ + public static String getTodayTableName() { + return getTableName(LocalDate.now()); + } + + /** + * 根据日期范围获取所有涉及的表名列表 + *

+ * 用于跨日期范围查询场景 + *

+ * + * @param beginDate 开始日期(含) + * @param endDate 结束日期(含) + * @return 表名列表 + */ + public static List getTableNames(Date beginDate, Date endDate) { + List tableNames = new ArrayList<>(); + + if (beginDate == null && endDate == null) { + // 无日期范围,返回当天表 + tableNames.add(getTodayTableName()); + return tableNames; + } + + // 处理单边日期的情况 + LocalDate start = beginDate != null + ? DateUtil.toLocalDateTime(beginDate).toLocalDate() + : LocalDate.now(); + LocalDate end = endDate != null + ? DateUtil.toLocalDateTime(endDate).toLocalDate() + : LocalDate.now(); + + // 确保 start <= end + if (start.isAfter(end)) { + LocalDate temp = start; + start = end; + end = temp; + } + + // 遍历日期范围生成表名 + LocalDate current = start; + while (!current.isAfter(end)) { + tableNames.add(getTableName(current)); + current = current.plusDays(1); + } + + return tableNames; + } + + /** + * 从表名解析日期后缀 + * + * @param tableName 表名 + * @return 日期字符串,如 20251126 + */ + public static String parseDateSuffix(String tableName) { + if (StrUtil.isBlank(tableName) || !tableName.startsWith(BASE_TABLE_NAME + "_")) { + return null; + } + return tableName.substring((BASE_TABLE_NAME + "_").length()); + } + + /** + * 检查表是否存在(带缓存) + *

+ * 使用缓存减少数据库元数据查询,存在的表会被缓存,不存在的表不缓存以便下次重新检查 + *

+ * + * @param tableName 表名 + * @return 是否存在 + */ + public static boolean checkTableExists(String tableName) { + // 检查缓存是否过期 + long now = System.currentTimeMillis(); + if (now - lastCacheRefreshTime > CACHE_EXPIRE_MS) { + TABLE_EXISTS_CACHE.clear(); + lastCacheRefreshTime = now; + } + + // 从缓存获取 + Boolean cached = TABLE_EXISTS_CACHE.get(tableName); + if (cached != null) { + return cached; + } + + // 查询数据库 + boolean exists = doCheckTableExists(tableName); + + // 只缓存存在的表(不存在的表可能后续会创建) + if (exists) { + TABLE_EXISTS_CACHE.put(tableName, Boolean.TRUE); + } + + return exists; + } + + /** + * 实际检查表是否存在(使用 information_schema 查询,比 JDBC 元数据更快) + */ + private static boolean doCheckTableExists(String tableName) { + try { + DataSource dataSource = SpringUtils.getBean(DataSource.class); + try (Connection conn = dataSource.getConnection()) { + // 使用 information_schema 查询,性能优于 DatabaseMetaData + String sql = "SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1"; + try (PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, tableName); + try (ResultSet rs = stmt.executeQuery()) { + return rs.next(); + } + } + } + } catch (Exception e) { + log.warn("检查表 {} 是否存在时出错: {}", tableName, e.getMessage()); + return false; + } + } + + /** + * 根据日期范围获取所有实际存在的表名列表 + *

+ * 只返回数据库中实际存在的分表 + *

+ * + * @param beginDate 开始日期(含) + * @param endDate 结束日期(含) + * @return 实际存在的表名列表 + */ + public static List getExistingTableNames(Date beginDate, Date endDate) { + List allTableNames = getTableNames(beginDate, endDate); + return allTableNames.stream() + .filter(RfidReadRecordTableHelper::checkTableExists) + .collect(Collectors.toList()); + } +} diff --git a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/mapper/RfidReadRecordMapper.java b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/mapper/RfidReadRecordMapper.java index d063c25..8fd4b80 100644 --- a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/mapper/RfidReadRecordMapper.java +++ b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/mapper/RfidReadRecordMapper.java @@ -12,6 +12,10 @@ import java.util.Collection; /** * 读取记录Mapper接口 + *

+ * 支持按日期分表,表名格式:rfid_read_record_yyyyMMdd + * 所有方法需传入动态表名参数 + *

* * @author zch * @date 2025-11-25 @@ -19,85 +23,252 @@ import java.util.Collection; public interface RfidReadRecordMapper extends BaseMapperPlus { /** - * 查询读取记录列表(自定义条件) + * 查询读取记录列表(单表) * + * @param tableName 表名 * @param queryWrapper 条件 * @return 读取记录集合 */ - List selectCustomRfidReadRecordVoList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + List selectCustomRfidReadRecordVoList( + @Param("tableName") String tableName, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 查询读取记录列表(多表联合查询,跨日期范围) + * + * @param tableNames 表名列表 + * @param queryWrapper 条件 + * @return 读取记录集合 + */ + List selectCustomRfidReadRecordVoListMultiTable( + @Param("tableNames") List tableNames, + @Param(Constants.WRAPPER) Wrapper queryWrapper); /** * 根据ID查询读取记录详情 * - * @param id 主键ID + * @param tableName 表名 + * @param id 主键ID * @return 读取记录对象 */ - RfidReadRecordVo selectCustomRfidReadRecordVoById(@Param("id") Long id); + RfidReadRecordVo selectCustomRfidReadRecordVoById( + @Param("tableName") String tableName, + @Param("id") Long id); /** * 根据ID列表批量查询读取记录 * - * @param ids ID集合 + * @param tableName 表名 + * @param ids ID集合 * @return 读取记录集合 */ - List selectCustomRfidReadRecordVoByIds(@Param("ids") Collection ids); + List selectCustomRfidReadRecordVoByIds( + @Param("tableName") String tableName, + @Param("ids") Collection ids); /** - * 统计读取记录记录数 + * 统计读取记录数(单表) * + * @param tableName 表名 * @param queryWrapper 查询条件 * @return 记录总数 */ - Long countCustomRfidReadRecord(@Param(Constants.WRAPPER) Wrapper queryWrapper); + Long countCustomRfidReadRecord( + @Param("tableName") String tableName, + @Param(Constants.WRAPPER) Wrapper queryWrapper); /** - * 分页查询读取记录(自定义条件) + * 统计读取记录数(多表) * + * @param tableNames 表名列表 + * @param queryWrapper 查询条件 + * @return 记录总数 + */ + Long countCustomRfidReadRecordMultiTable( + @Param("tableNames") List tableNames, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 分页查询读取记录(单表) + * + * @param tableName 表名 * @param page 分页对象 * @param queryWrapper 查询条件 * @return 分页结果 */ - Page selectCustomRfidReadRecordVoPage(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + Page selectCustomRfidReadRecordVoPage( + @Param("tableName") String tableName, + @Param("page") Page page, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 分页查询读取记录(多表联合查询,跨日期范围) + * + * @param tableNames 表名列表 + * @param page 分页对象 + * @param queryWrapper 查询条件 + * @return 分页结果 + */ + Page selectCustomRfidReadRecordVoPageMultiTable( + @Param("tableNames") List tableNames, + @Param("page") Page page, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 单条插入读取记录 + * + * @param tableName 表名 + * @param entity 读取记录对象 + * @return 影响行数 + */ + int insertRfidReadRecord( + @Param("tableName") String tableName, + @Param("entity") RfidReadRecord entity); /** * 批量插入读取记录 * - * @param list 读取记录对象集合 + * @param tableName 表名 + * @param list 读取记录对象集合 * @return 影响行数 */ - int batchInsertRfidReadRecord(@Param("list") List list); + int batchInsertRfidReadRecord( + @Param("tableName") String tableName, + @Param("list") List list); + + /** + * 单条更新读取记录 + * + * @param tableName 表名 + * @param entity 读取记录对象 + * @return 影响行数 + */ + int updateRfidReadRecordById( + @Param("tableName") String tableName, + @Param("entity") RfidReadRecord entity); /** * 批量更新读取记录 * - * @param list 读取记录对象集合 + * @param tableName 表名 + * @param list 读取记录对象集合 * @return 影响行数 */ - int batchUpdateRfidReadRecord(@Param("list") List list); + int batchUpdateRfidReadRecord( + @Param("tableName") String tableName, + @Param("list") List list); /** * 根据自定义条件删除读取记录 * + * @param tableName 表名 * @param queryWrapper 删除条件 * @return 影响行数 */ - int deleteCustomRfidReadRecord(@Param(Constants.WRAPPER) Wrapper queryWrapper); + int deleteCustomRfidReadRecord( + @Param("tableName") String tableName, + @Param(Constants.WRAPPER) Wrapper queryWrapper); /** * 根据ID列表批量删除读取记录 * - * @param ids ID集合 + * @param tableName 表名 + * @param ids ID集合 * @return 影响行数 */ - int deleteCustomRfidReadRecordByIds(@Param("ids") Collection ids); + int deleteCustomRfidReadRecordByIds( + @Param("tableName") String tableName, + @Param("ids") Collection ids); /** - * 检查读取记录是否存在 + * 检查读取记录是否存在(单表) * + * @param tableName 表名 * @param queryWrapper 查询条件 * @return 是否存在 */ - Boolean existsRfidReadRecord(@Param(Constants.WRAPPER) Wrapper queryWrapper); + Boolean existsRfidReadRecord( + @Param("tableName") String tableName, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + /** + * 检查读取记录是否存在(多表) + * + * @param tableNames 表名列表 + * @param queryWrapper 查询条件 + * @return 是否存在 + */ + Boolean existsRfidReadRecordMultiTable( + @Param("tableNames") List tableNames, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + // ==================== 看板专用查询方法 ==================== + + /** + * 查询每个设备的最新一条读取记录 + * + * @param tableName 表名 + * @param deviceIds 设备ID列表(可选) + * @return 最新读取记录列表 + */ + List selectLatestRecordByDevice( + @Param("tableName") String tableName, + @Param("deviceIds") List deviceIds); + + /** + * 按小时统计成功率 + * + * @param tableName 表名 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 成功率统计结果 + */ + List> selectSuccessRateByHour( + @Param("tableName") String tableName, + @Param("startTime") String startTime, + @Param("endTime") String endTime); + + /** + * 查询告警记录列表 + * + * @param tableName 表名 + * @param limit 限制数量 + * @return 告警记录列表 + */ + List selectAlarmRecordList( + @Param("tableName") String tableName, + @Param("limit") Integer limit); + + // ==================== 采样查询方法(无索引依赖,大数据量优化) ==================== + + /** + * 采样查询读取记录列表(多表) + *

+ * 使用时间槽分组,每 N 分钟取一条代表数据,适用于大数据量场景 + * 无索引依赖,通过 UNIX_TIMESTAMP + FLOOR 计算时间槽 + *

+ * + * @param tableNames 表名列表 + * @param samplingInterval 采样间隔(分钟) + * @param queryWrapper 查询条件 + * @return 采样后的记录列表 + */ + List selectWithSamplingMultiTable( + @Param("tableNames") List tableNames, + @Param("samplingInterval") Integer samplingInterval, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 采样查询读取记录列表(单表) + * + * @param tableName 表名 + * @param samplingInterval 采样间隔(分钟) + * @param queryWrapper 查询条件 + * @return 采样后的记录列表 + */ + List selectWithSampling( + @Param("tableName") String tableName, + @Param("samplingInterval") Integer samplingInterval, + @Param(Constants.WRAPPER) Wrapper queryWrapper); } diff --git a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/service/impl/RfidReadRecordServiceImpl.java b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/service/impl/RfidReadRecordServiceImpl.java index debe036..99e6317 100644 --- a/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/service/impl/RfidReadRecordServiceImpl.java +++ b/ruoyi-modules/hw-rfid/src/main/java/org/dromara/rfid/service/impl/RfidReadRecordServiceImpl.java @@ -1,5 +1,8 @@ package org.dromara.rfid.service.impl; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; +import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -9,19 +12,24 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.dromara.rfid.helper.RfidReadRecordTableHelper; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.dromara.rfid.domain.bo.RfidReadRecordBo; import org.dromara.rfid.domain.vo.RfidReadRecordVo; import org.dromara.rfid.domain.RfidReadRecord; import org.dromara.rfid.mapper.RfidReadRecordMapper; import org.dromara.rfid.service.IRfidReadRecordService; +import java.util.Date; import java.util.List; -import java.util.Map; import java.util.Collection; /** * 读取记录Service业务层处理 + *

+ * 支持按日期分表,表名格式:rfid_read_record_yyyyMMdd + *

* * @author zch * @date 2025-11-25 @@ -36,16 +44,22 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService { /** * 查询读取记录 * - * @param id 主键 + * @param id 主键 + * @param queryDate 查询日期(用于定位分表) * @return 读取记录 */ @Override - public RfidReadRecordVo queryById(Long id){ - return baseMapper.selectCustomRfidReadRecordVoById(id); + public RfidReadRecordVo queryById(Long id, Date queryDate) { + String tableName = RfidReadRecordTableHelper.getTableName(queryDate); + return baseMapper.selectCustomRfidReadRecordVoById(tableName, id); } /** * 分页查询读取记录列表 + *

+ * 根据 beginRecordTime 和 endRecordTime 确定查询的分表范围, + * 只查询实际存在的分表 + *

* * @param bo 查询条件 * @param pageQuery 分页参数 @@ -53,33 +67,83 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService { */ @Override public TableDataInfo queryPageList(RfidReadRecordBo bo, PageQuery pageQuery) { + // 获取实际存在的分表列表 + List tableNames = RfidReadRecordTableHelper.getExistingTableNames( + bo.getBeginRecordTime(), bo.getEndRecordTime()); + + // 没有存在的分表,返回空数据 + if (CollUtil.isEmpty(tableNames)) { + return TableDataInfo.build(new Page<>()); + } + LambdaQueryWrapper lqw = buildQueryWrapper(bo); - // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 进行分页查询 - Page result = baseMapper.selectCustomRfidReadRecordVoPage(pageQuery.build(), lqw); + Page result = tableNames.size() == 1 + ? baseMapper.selectCustomRfidReadRecordVoPage(tableNames.get(0), pageQuery.build(), lqw) + : baseMapper.selectCustomRfidReadRecordVoPageMultiTable(tableNames, pageQuery.build(), lqw); + return TableDataInfo.build(result); } /** * 查询符合条件的读取记录列表 + *

+ * 只查询实际存在的分表。 + * 支持采样查询:当 samplingInterval > 1 时,每 N 分钟取一条代表数据 + *

* * @param bo 查询条件 * @return 读取记录列表 */ @Override public List queryList(RfidReadRecordBo bo) { + // 获取实际存在的分表列表 + List tableNames = RfidReadRecordTableHelper.getExistingTableNames( + bo.getBeginRecordTime(), bo.getEndRecordTime()); + + if (CollUtil.isEmpty(tableNames)) { + return List.of(); + } + LambdaQueryWrapper lqw = buildQueryWrapper(bo); - // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 查询列表 - return baseMapper.selectCustomRfidReadRecordVoList(lqw); + + // 判断是否使用采样查询(无索引依赖,大数据量优化) + Integer samplingInterval = bo.getSamplingInterval(); + if (samplingInterval != null && samplingInterval > 1) { + // 采样查询:每 N 分钟取一条代表数据 + // tableNames.size() == 1 表示只涉及一张分表,直接调用单表采样SQL; + // 否则说明跨多天/多张分表,调用多表采样SQL进行 UNION ALL 聚合。 + return tableNames.size() == 1 + ? baseMapper.selectWithSampling(tableNames.get(0), samplingInterval, lqw) + : baseMapper.selectWithSamplingMultiTable(tableNames, samplingInterval, lqw); + } + + // 普通查询 + return tableNames.size() == 1 + ? baseMapper.selectCustomRfidReadRecordVoList(tableNames.get(0), lqw) + : baseMapper.selectCustomRfidReadRecordVoListMultiTable(tableNames, lqw); } + /** + * 构建查询条件 + *

+ * 注意:排序在 Mapper XML 中控制,这里只构建 WHERE 条件 + * 条件顺序:优先使用索引字段(device_id, record_time) + *

+ */ private LambdaQueryWrapper buildQueryWrapper(RfidReadRecordBo bo) { - Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.orderByAsc(RfidReadRecord::getId); + // 优先条件:设备ID(索引字段) lqw.eq(bo.getDeviceId() != null, RfidReadRecord::getDeviceId, bo.getDeviceId()); + // 时间范围查询(索引字段),使用 BETWEEN 更高效 + if (bo.getBeginRecordTime() != null && bo.getEndRecordTime() != null) { + lqw.between(RfidReadRecord::getRecordTime, bo.getBeginRecordTime(), bo.getEndRecordTime()); + } else { + lqw.ge(bo.getBeginRecordTime() != null, RfidReadRecord::getRecordTime, bo.getBeginRecordTime()); + lqw.le(bo.getEndRecordTime() != null, RfidReadRecord::getRecordTime, bo.getEndRecordTime()); + } + // 其他条件 lqw.eq(StringUtils.isNotBlank(bo.getReadStatus()), RfidReadRecord::getReadStatus, bo.getReadStatus()); lqw.eq(StringUtils.isNotBlank(bo.getBarcode()), RfidReadRecord::getBarcode, bo.getBarcode()); - lqw.eq(bo.getRecordTime() != null, RfidReadRecord::getRecordTime, bo.getRecordTime()); lqw.eq(StringUtils.isNotBlank(bo.getAlarmFlag()), RfidReadRecord::getAlarmFlag, bo.getAlarmFlag()); lqw.eq(StringUtils.isNotBlank(bo.getAlarmLevel()), RfidReadRecord::getAlarmLevel, bo.getAlarmLevel()); lqw.eq(StringUtils.isNotBlank(bo.getAlarmType()), RfidReadRecord::getAlarmType, bo.getAlarmType()); @@ -89,15 +153,27 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService { /** * 新增读取记录 + *

+ * 根据 recordTime 自动路由到对应日期的分表 + *

* * @param bo 读取记录 * @return 是否新增成功 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean insertByBo(RfidReadRecordBo bo) { RfidReadRecord add = MapstructUtils.convert(bo, RfidReadRecord.class); validEntityBeforeSave(add); - boolean flag = baseMapper.insert(add) > 0; + + // 自定义 SQL 不会自动生成 ID,需要手动生成雪花 ID + if (add.getId() == null) { + add.setId(IdUtil.getSnowflakeNextId()); + } + + // 根据 recordTime 路由到对应分表 + String tableName = RfidReadRecordTableHelper.getTableName(add.getRecordTime()); + boolean flag = baseMapper.insertRfidReadRecord(tableName, add) > 0; if (flag) { bo.setId(add.getId()); } @@ -106,36 +182,56 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService { /** * 修改读取记录 + *

+ * 根据 recordTime 定位分表进行更新 + *

* * @param bo 读取记录 * @return 是否修改成功 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean updateByBo(RfidReadRecordBo bo) { RfidReadRecord update = MapstructUtils.convert(bo, RfidReadRecord.class); validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; + + // 根据 recordTime 路由到对应分表 + String tableName = RfidReadRecordTableHelper.getTableName(update.getRecordTime()); + return baseMapper.updateRfidReadRecordById(tableName, update) > 0; } /** * 保存前的数据校验 */ - private void validEntityBeforeSave(RfidReadRecord entity){ - //TODO 做一些数据校验,如唯一约束 + private void validEntityBeforeSave(RfidReadRecord entity) { + if (entity == null) { + throw new ServiceException("读取记录不能为空"); + } + + if (entity.getBarcode() != null) { + entity.setBarcode(entity.getBarcode().trim()); + } + + if ("1".equals(entity.getReadStatus()) && StringUtils.isBlank(entity.getBarcode())) { + throw new ServiceException("读取成功时条码信息不能为空"); + } } /** * 校验并批量删除读取记录信息 * - * @param ids 待删除的主键集合 - * @param isValid 是否进行有效性校验 + * @param ids 待删除的主键集合 + * @param queryDate 查询日期(用于定位分表) + * @param isValid 是否进行有效性校验 * @return 是否删除成功 */ @Override - public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + @Transactional(rollbackFor = Exception.class) + public Boolean deleteWithValidByIds(Collection ids, Date queryDate, Boolean isValid) { + if (CollUtil.isEmpty(ids)) { + return Boolean.TRUE; } - return baseMapper.deleteByIds(ids) > 0; + String tableName = RfidReadRecordTableHelper.getTableName(queryDate); + return baseMapper.deleteCustomRfidReadRecordByIds(tableName, ids) > 0; } } diff --git a/ruoyi-modules/hw-rfid/src/main/resources/mapper/rfid/RfidReadRecordMapper.xml b/ruoyi-modules/hw-rfid/src/main/resources/mapper/rfid/RfidReadRecordMapper.xml index 6f73ded..6db5f6a 100644 --- a/ruoyi-modules/hw-rfid/src/main/resources/mapper/rfid/RfidReadRecordMapper.xml +++ b/ruoyi-modules/hw-rfid/src/main/resources/mapper/rfid/RfidReadRecordMapper.xml @@ -7,6 +7,7 @@ + + + + + @@ -39,7 +76,7 @@ t.alarm_level, t.alarm_type, t.alarm_action - from rfid_read_record t + from ${tableName} t left join rfid_device d on t.device_id = d.id where t.id = #{id} @@ -57,7 +94,7 @@ t.alarm_level, t.alarm_type, t.alarm_action - from rfid_read_record t + from ${tableName} t left join rfid_device d on t.device_id = d.id where t.id in @@ -65,15 +102,29 @@ - + - + + + + + + + + + + + insert into ${tableName}( + id, + device_id, + read_status, + barcode, + record_time, + alarm_flag, + alarm_level, + alarm_type, + alarm_action + ) + values ( + #{entity.id}, + #{entity.deviceId}, + #{entity.readStatus}, + #{entity.barcode}, + #{entity.recordTime}, + #{entity.alarmFlag}, + #{entity.alarmLevel}, + #{entity.alarmType}, + #{entity.alarmAction} + ) + + - insert into rfid_read_record( + insert into ${tableName}( + id, device_id, - read_status, - barcode, - record_time, - alarm_flag, - alarm_level, - alarm_type, - alarm_action - ) values ( + #{item.id}, #{item.deviceId}, - #{item.readStatus}, - #{item.barcode}, - #{item.recordTime}, - #{item.alarmFlag}, - #{item.alarmLevel}, - #{item.alarmType}, - #{item.alarmAction} - ) + + + update ${tableName} + + + device_id = #{entity.deviceId}, + + + read_status = #{entity.readStatus}, + + + barcode = #{entity.barcode}, + + + record_time = #{entity.recordTime}, + + + alarm_flag = #{entity.alarmFlag}, + + + alarm_level = #{entity.alarmLevel}, + + + alarm_type = #{entity.alarmType}, + + + alarm_action = #{entity.alarmAction}, + + + where id = #{entity.id} + + - update rfid_read_record + update ${tableName} device_id = #{item.deviceId}, @@ -147,23 +278,23 @@ read_status = #{item.readStatus}, - + barcode = #{item.barcode}, record_time = #{item.recordTime}, - + alarm_flag = #{item.alarmFlag}, - + alarm_level = #{item.alarmLevel}, - + alarm_type = #{item.alarmType}, - - alarm_action = #{item.alarmAction} + + alarm_action = #{item.alarmAction}, where id = #{item.id} @@ -172,7 +303,7 @@ - delete from rfid_read_record + delete from ${tableName} ${ew.customSqlSegment} @@ -180,20 +311,185 @@ - delete from rfid_read_record + delete from ${tableName} where id in #{id} - + + + + + + + + + + + + + + + + + + + + + + + +