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}
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+