feat(rfid/RfidReadRecord): 支持按日期分表查询与操作

- 读取记录实体支持动态表名,不使用内置CRUD,改为自定义SQL
- 查询参数新增时间范围、采样间隔支持大数据量降采样查询
- Controller接口支持按查询日期定位分表
- Mapper接口及XML新增多表联合查询、采样查询、告警查询等方法
- Service实现根据时间自动路由到对应分表,支持分页及采样查询优化
- 新增根据设备查询最新记录及按小时统计成功率的看板查询方法
- 数据库操作新增批量插入、批量更新、批量删除支持动态表名
- 采用 EXISTS 优化判断记录存在性避免全表扫描
- 统一去除空字符串判断,支持null字段条件过滤
- 增加实体与业务对象校验,读取成功时条码必填
- 插入时手动生成雪花ID,保证数据唯一性
- 删除操作支持传入查询日期定向删除指定分表数据
main
zangch@mesnac.com 4 months ago
parent 5a004ae7db
commit f048869aa9

@ -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;
/**
*
* <p>
* rfid_read_record_yyyyMMdd
* </p>
*
* @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<RfidReadRecordVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(rfidReadRecordService.queryById(id));
public R<RfidReadRecordVo> 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<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(rfidReadRecordService.deleteWithValidByIds(List.of(ids), true));
public R<Void> 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));
}
/**

@ -10,10 +10,19 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
/**
* rfid_read_record
*
* <p>
* rfid_read_record_yyyyMMdd
* rfid_read_record_20251126
* </p>
* <p>
* 使 MyBatis-Plus CRUD
* Mapper XML SQL
* </p>
*
* @author zch
* @date 2025-11-25
* @see org.dromara.rfid.helper.RfidReadRecordTableHelper
*/
@Data
@EqualsAndHashCode(callSuper = true)

@ -12,7 +12,10 @@ import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* rfid_read_record
*
* <p>
* rfid_read_record_yyyyMMdd
* </p>
*
* @author zch
* @date 2025-11-25
@ -42,16 +45,31 @@ public class RfidReadRecordBo extends BaseEntity {
/**
*
* <p>
*
* </p>
*/
@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;
/**
*
* <p>
*
* - null <= 1
* - > 1 N
* </p>
*/
private Integer samplingInterval;
}

@ -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;
/**

@ -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
* <p>
* rfid_read_record_yyyyMMdd
* </p>
*
* @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便
* <p>
* 使 ConcurrentHashMap 线
* </p>
*/
private static final Map<String, Boolean> 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() {
}
/**
*
* <p>
*
* </p>
*/
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());
}
/**
*
* <p>
*
* </p>
*
* @param beginDate
* @param endDate
* @return
*/
public static List<String> getTableNames(Date beginDate, Date endDate) {
List<String> 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());
}
/**
*
* <p>
* 使便
* </p>
*
* @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;
}
}
/**
*
* <p>
*
* </p>
*
* @param beginDate
* @param endDate
* @return
*/
public static List<String> getExistingTableNames(Date beginDate, Date endDate) {
List<String> allTableNames = getTableNames(beginDate, endDate);
return allTableNames.stream()
.filter(RfidReadRecordTableHelper::checkTableExists)
.collect(Collectors.toList());
}
}

@ -12,6 +12,10 @@ import java.util.Collection;
/**
* Mapper
* <p>
* rfid_read_record_yyyyMMdd
*
* </p>
*
* @author zch
* @date 2025-11-25
@ -19,85 +23,252 @@ import java.util.Collection;
public interface RfidReadRecordMapper extends BaseMapperPlus<RfidReadRecord, RfidReadRecordVo> {
/**
*
*
*
* @param tableName
* @param queryWrapper
* @return
*/
List<RfidReadRecordVo> selectCustomRfidReadRecordVoList(@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
List<RfidReadRecordVo> selectCustomRfidReadRecordVoList(
@Param("tableName") String tableName,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
/**
*
*
* @param tableNames
* @param queryWrapper
* @return
*/
List<RfidReadRecordVo> selectCustomRfidReadRecordVoListMultiTable(
@Param("tableNames") List<String> tableNames,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> 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<RfidReadRecordVo> selectCustomRfidReadRecordVoByIds(@Param("ids") Collection<Long> ids);
List<RfidReadRecordVo> selectCustomRfidReadRecordVoByIds(
@Param("tableName") String tableName,
@Param("ids") Collection<Long> ids);
/**
*
*
*
* @param tableName
* @param queryWrapper
* @return
*/
Long countCustomRfidReadRecord(@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
Long countCustomRfidReadRecord(
@Param("tableName") String tableName,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
/**
*
*
*
* @param tableNames
* @param queryWrapper
* @return
*/
Long countCustomRfidReadRecordMultiTable(
@Param("tableNames") List<String> tableNames,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
/**
*
*
* @param tableName
* @param page
* @param queryWrapper
* @return
*/
Page<RfidReadRecordVo> selectCustomRfidReadRecordVoPage(@Param("page") Page<RfidReadRecord> page, @Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
Page<RfidReadRecordVo> selectCustomRfidReadRecordVoPage(
@Param("tableName") String tableName,
@Param("page") Page<RfidReadRecord> page,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
/**
*
*
* @param tableNames
* @param page
* @param queryWrapper
* @return
*/
Page<RfidReadRecordVo> selectCustomRfidReadRecordVoPageMultiTable(
@Param("tableNames") List<String> tableNames,
@Param("page") Page<RfidReadRecord> page,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> 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<RfidReadRecord> list);
int batchInsertRfidReadRecord(
@Param("tableName") String tableName,
@Param("list") List<RfidReadRecord> 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<RfidReadRecord> list);
int batchUpdateRfidReadRecord(
@Param("tableName") String tableName,
@Param("list") List<RfidReadRecord> list);
/**
*
*
* @param tableName
* @param queryWrapper
* @return
*/
int deleteCustomRfidReadRecord(@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
int deleteCustomRfidReadRecord(
@Param("tableName") String tableName,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
/**
* ID
*
* @param ids ID
* @param tableName
* @param ids ID
* @return
*/
int deleteCustomRfidReadRecordByIds(@Param("ids") Collection<Long> ids);
int deleteCustomRfidReadRecordByIds(
@Param("tableName") String tableName,
@Param("ids") Collection<Long> ids);
/**
*
*
*
* @param tableName
* @param queryWrapper
* @return
*/
Boolean existsRfidReadRecord(@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
Boolean existsRfidReadRecord(
@Param("tableName") String tableName,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
/**
*
*
* @param tableNames
* @param queryWrapper
* @return
*/
Boolean existsRfidReadRecordMultiTable(
@Param("tableNames") List<String> tableNames,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
// ==================== 看板专用查询方法 ====================
/**
*
*
* @param tableName
* @param deviceIds ID
* @return
*/
List<RfidReadRecordVo> selectLatestRecordByDevice(
@Param("tableName") String tableName,
@Param("deviceIds") List<Long> deviceIds);
/**
*
*
* @param tableName
* @param startTime
* @param endTime
* @return
*/
List<java.util.Map<String, Object>> selectSuccessRateByHour(
@Param("tableName") String tableName,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
/**
*
*
* @param tableName
* @param limit
* @return
*/
List<RfidReadRecordVo> selectAlarmRecordList(
@Param("tableName") String tableName,
@Param("limit") Integer limit);
// ==================== 采样查询方法(无索引依赖,大数据量优化) ====================
/**
*
* <p>
* 使 N
* UNIX_TIMESTAMP + FLOOR
* </p>
*
* @param tableNames
* @param samplingInterval
* @param queryWrapper
* @return
*/
List<RfidReadRecordVo> selectWithSamplingMultiTable(
@Param("tableNames") List<String> tableNames,
@Param("samplingInterval") Integer samplingInterval,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
/**
*
*
* @param tableName
* @param samplingInterval
* @param queryWrapper
* @return
*/
List<RfidReadRecordVo> selectWithSampling(
@Param("tableName") String tableName,
@Param("samplingInterval") Integer samplingInterval,
@Param(Constants.WRAPPER) Wrapper<RfidReadRecord> queryWrapper);
}

@ -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
* <p>
* rfid_read_record_yyyyMMdd
* </p>
*
* @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);
}
/**
*
* <p>
* beginRecordTime endRecordTime
*
* </p>
*
* @param bo
* @param pageQuery
@ -53,33 +67,83 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService {
*/
@Override
public TableDataInfo<RfidReadRecordVo> queryPageList(RfidReadRecordBo bo, PageQuery pageQuery) {
// 获取实际存在的分表列表
List<String> tableNames = RfidReadRecordTableHelper.getExistingTableNames(
bo.getBeginRecordTime(), bo.getEndRecordTime());
// 没有存在的分表,返回空数据
if (CollUtil.isEmpty(tableNames)) {
return TableDataInfo.build(new Page<>());
}
LambdaQueryWrapper<RfidReadRecord> lqw = buildQueryWrapper(bo);
// 使用自定义 Mapper XML + MyBatis-Plus Wrapper 进行分页查询
Page<RfidReadRecordVo> result = baseMapper.selectCustomRfidReadRecordVoPage(pageQuery.build(), lqw);
Page<RfidReadRecordVo> result = tableNames.size() == 1
? baseMapper.selectCustomRfidReadRecordVoPage(tableNames.get(0), pageQuery.build(), lqw)
: baseMapper.selectCustomRfidReadRecordVoPageMultiTable(tableNames, pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
*
* <p>
*
* samplingInterval > 1 N
* </p>
*
* @param bo
* @return
*/
@Override
public List<RfidReadRecordVo> queryList(RfidReadRecordBo bo) {
// 获取实际存在的分表列表
List<String> tableNames = RfidReadRecordTableHelper.getExistingTableNames(
bo.getBeginRecordTime(), bo.getEndRecordTime());
if (CollUtil.isEmpty(tableNames)) {
return List.of();
}
LambdaQueryWrapper<RfidReadRecord> 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);
}
/**
*
* <p>
* Mapper XML WHERE
* 使device_id, record_time
* </p>
*/
private LambdaQueryWrapper<RfidReadRecord> buildQueryWrapper(RfidReadRecordBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<RfidReadRecord> 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 {
/**
*
* <p>
* recordTime
* </p>
*
* @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 {
/**
*
* <p>
* recordTime
* </p>
*
* @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<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> 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;
}
}

@ -7,6 +7,7 @@
<resultMap type="org.dromara.rfid.domain.vo.RfidReadRecordVo" id="RfidReadRecordResult">
</resultMap>
<!-- 查询列表(单表) -->
<select id="selectCustomRfidReadRecordVoList" resultMap="RfidReadRecordResult">
select t.id,
t.device_id,
@ -19,11 +20,47 @@
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
<if test="ew != null">
${ew.customSqlSegment}
</if>
order by t.record_time desc
</select>
<!-- 查询列表(多表联合查询,跨日期范围) -->
<!-- 优化:先在子查询中过滤数据,再关联设备表,减少 JOIN 数据量 -->
<select id="selectCustomRfidReadRecordVoListMultiTable" resultMap="RfidReadRecordResult">
select combined.id,
combined.device_id,
d.device_code as deviceCode,
d.device_name as deviceName,
combined.read_status,
combined.barcode,
combined.record_time,
combined.alarm_flag,
combined.alarm_level,
combined.alarm_type,
combined.alarm_action
from (
<foreach collection="tableNames" item="tbl" separator=" UNION ALL ">
select t.id,
t.device_id,
t.read_status,
t.barcode,
t.record_time,
t.alarm_flag,
t.alarm_level,
t.alarm_type,
t.alarm_action
from ${tbl} t
<if test="ew != null">
${ew.customSqlSegment}
</if>
</foreach>
) combined
left join rfid_device d on combined.device_id = d.id
order by combined.record_time desc
</select>
<!-- 根据ID查询详情 -->
@ -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}
</select>
@ -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
<foreach collection="ids" item="id" open="(" separator="," close=")">
@ -65,15 +102,29 @@
</foreach>
</select>
<!-- 统计查询 -->
<!-- 统计查询(单表) -->
<select id="countCustomRfidReadRecord" resultType="java.lang.Long">
select count(1) from rfid_read_record t
select count(1)
from ${tableName} t
<if test="ew != null">
${ew.customSqlSegment}
</if>
</select>
<!-- 分页查询(带自定义条件) -->
<!-- 统计查询(多表) -->
<select id="countCustomRfidReadRecordMultiTable" resultType="java.lang.Long">
select sum(cnt)
from (
<foreach collection="tableNames" item="tbl" separator=" UNION ALL ">
select count(1) as cnt from ${tbl} t
<if test="ew != null">
${ew.customSqlSegment}
</if>
</foreach>
) tmp
</select>
<!-- 分页查询(单表) -->
<select id="selectCustomRfidReadRecordVoPage" resultMap="RfidReadRecordResult">
select t.id,
t.device_id,
@ -86,60 +137,140 @@
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
<if test="ew != null">
${ew.customSqlSegment}
</if>
order by t.record_time desc
</select>
<!-- 分页查询(多表联合查询,跨日期范围) -->
<!-- 优化:先在子查询中过滤数据,再关联设备表,减少 JOIN 数据量 -->
<select id="selectCustomRfidReadRecordVoPageMultiTable" resultMap="RfidReadRecordResult">
select combined.id,
combined.device_id,
d.device_code as deviceCode,
d.device_name as deviceName,
combined.read_status,
combined.barcode,
combined.record_time,
combined.alarm_flag,
combined.alarm_level,
combined.alarm_type,
combined.alarm_action
from (
<foreach collection="tableNames" item="tbl" separator=" UNION ALL ">
select t.id,
t.device_id,
t.read_status,
t.barcode,
t.record_time,
t.alarm_flag,
t.alarm_level,
t.alarm_type,
t.alarm_action
from ${tbl} t
<if test="ew != null">
${ew.customSqlSegment}
</if>
</foreach>
) combined
left join rfid_device d on combined.device_id = d.id
order by combined.record_time desc
</select>
<!-- 单条插入 -->
<insert id="insertRfidReadRecord">
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>
<!-- 批量插入 -->
<insert id="batchInsertRfidReadRecord">
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
<foreach collection="list" item="item" separator=",">
(
#{item.id},
#{item.deviceId},
#{item.readStatus},
#{item.barcode},
#{item.recordTime},
#{item.alarmFlag},
#{item.alarmLevel},
#{item.alarmType},
#{item.alarmAction}
)
</foreach>
</insert>
<!-- 单条更新 -->
<update id="updateRfidReadRecordById">
update ${tableName}
<set>
<if test="entity.deviceId != null">
device_id = #{entity.deviceId},
</if>
<if test="entity.readStatus != null and entity.readStatus != ''">
read_status = #{entity.readStatus},
</if>
<if test="entity.barcode != null">
barcode = #{entity.barcode},
</if>
<if test="entity.recordTime != null">
record_time = #{entity.recordTime},
</if>
<if test="entity.alarmFlag != null">
alarm_flag = #{entity.alarmFlag},
</if>
<if test="entity.alarmLevel != null">
alarm_level = #{entity.alarmLevel},
</if>
<if test="entity.alarmType != null">
alarm_type = #{entity.alarmType},
</if>
<if test="entity.alarmAction != null">
alarm_action = #{entity.alarmAction},
</if>
</set>
where id = #{entity.id}
</update>
<!-- 批量更新 -->
<update id="batchUpdateRfidReadRecord">
<foreach collection="list" item="item" separator=";">
update rfid_read_record
update ${tableName}
<set>
<if test="item.deviceId != null">
device_id = #{item.deviceId},
@ -147,23 +278,23 @@
<if test="item.readStatus != null and item.readStatus != ''">
read_status = #{item.readStatus},
</if>
<if test="item.barcode != null and item.barcode != ''">
<if test="item.barcode != null">
barcode = #{item.barcode},
</if>
<if test="item.recordTime != null">
record_time = #{item.recordTime},
</if>
<if test="item.alarmFlag != null and item.alarmFlag != ''">
<if test="item.alarmFlag != null">
alarm_flag = #{item.alarmFlag},
</if>
<if test="item.alarmLevel != null and item.alarmLevel != ''">
<if test="item.alarmLevel != null">
alarm_level = #{item.alarmLevel},
</if>
<if test="item.alarmType != null and item.alarmType != ''">
<if test="item.alarmType != null">
alarm_type = #{item.alarmType},
</if>
<if test="item.alarmAction != null and item.alarmAction != ''">
alarm_action = #{item.alarmAction}
<if test="item.alarmAction != null">
alarm_action = #{item.alarmAction},
</if>
</set>
where id = #{item.id}
@ -172,7 +303,7 @@
<!-- 根据自定义条件删除 -->
<delete id="deleteCustomRfidReadRecord">
delete from rfid_read_record
delete from ${tableName}
<if test="ew != null">
${ew.customSqlSegment}
</if>
@ -180,20 +311,185 @@
<!-- 根据ID列表批量删除 -->
<delete id="deleteCustomRfidReadRecordByIds">
delete from rfid_read_record
delete from ${tableName}
where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 检查是否存在 -->
<!-- 检查是否存在(单表) -->
<!-- 优化:使用 EXISTS + LIMIT 1找到一条即返回 -->
<select id="existsRfidReadRecord" resultType="java.lang.Boolean">
select count(1) > 0 from rfid_read_record t
<if test="ew != null">
${ew.customSqlSegment}
select exists (
select 1 from ${tableName} t
<if test="ew != null">
${ew.customSqlSegment}
</if>
limit 1
)
</select>
<!-- 检查是否存在(多表) -->
<!-- 优化:使用 EXISTS + LIMIT 1找到一条即返回避免全表扫描 -->
<select id="existsRfidReadRecordMultiTable" resultType="java.lang.Boolean">
select exists (
select 1 from (
<foreach collection="tableNames" item="tbl" separator=" UNION ALL ">
select 1 from ${tbl} t
<if test="ew != null">
${ew.customSqlSegment}
</if>
limit 1
</foreach>
) tmp limit 1
)
</select>
<!-- ==================== 看板专用查询 ==================== -->
<!-- 查询每个设备的最新一条读取记录 -->
<select id="selectLatestRecordByDevice" resultMap="RfidReadRecordResult">
select t.id,
t.device_id,
d.device_code as deviceCode,
d.device_name as deviceName,
t.read_status,
t.barcode,
t.record_time,
t.alarm_flag,
t.alarm_level,
t.alarm_type,
t.alarm_action
from ${tableName} t
inner join (
select device_id, max(record_time) as max_time
from ${tableName}
<where>
<if test="deviceIds != null and deviceIds.size() > 0">
device_id in
<foreach collection="deviceIds" item="deviceId" open="(" separator="," close=")">
#{deviceId}
</foreach>
</if>
</where>
group by device_id
) latest on t.device_id = latest.device_id and t.record_time = latest.max_time
left join rfid_device d on t.device_id = d.id
order by t.device_id
</select>
<!-- 按小时统计成功率 -->
<select id="selectSuccessRateByHour" resultType="java.util.Map">
select
DATE_FORMAT(record_time, '%H:00') as timePoint,
ROUND(SUM(CASE WHEN read_status = '1' THEN 1 ELSE 0 END) * 100.0 / COUNT(1), 2) as successRate,
COUNT(1) as totalCount,
SUM(CASE WHEN read_status = '1' THEN 1 ELSE 0 END) as successCount
from ${tableName}
where record_time between #{startTime} and #{endTime}
group by DATE_FORMAT(record_time, '%H:00')
order by timePoint
</select>
<!-- 查询告警记录列表 -->
<select id="selectAlarmRecordList" resultMap="RfidReadRecordResult">
select t.id,
t.device_id,
d.device_code as deviceCode,
d.device_name as deviceName,
l.location_alias as locationAlias,
t.read_status,
t.barcode,
t.record_time,
t.alarm_flag,
t.alarm_level,
t.alarm_type,
t.alarm_action
from ${tableName} t
left join rfid_device d on t.device_id = d.id
left join rfid_location l on d.location_id = l.id
where t.alarm_flag = '1'
order by t.record_time desc
<if test="limit != null">
limit #{limit}
</if>
</select>
<!-- ==================== 采样查询(无索引依赖,大数据量优化) ==================== -->
<!-- 采样查询(单表):每 N 分钟取一条代表数据 -->
<select id="selectWithSampling" resultMap="RfidReadRecordResult">
select t1.id,
t1.device_id,
d.device_code as deviceCode,
d.device_name as deviceName,
t1.read_status,
t1.barcode,
t1.record_time,
t1.alarm_flag,
t1.alarm_level,
t1.alarm_type,
t1.alarm_action
from ${tableName} t1
left join rfid_device d on t1.device_id = d.id
inner join (
<!-- 子查询:按 device_id + time_slot 分组,取每个时间槽的最新记录 -->
select
t.device_id,
<!-- 使用 UNIX_TIMESTAMP 将时间转为秒,再除以采样间隔(分钟*60并取 FLOOR得到离散的时间槽编号 -->
FLOOR(UNIX_TIMESTAMP(t.record_time) / (#{samplingInterval} * 60)) as time_slot,
MAX(t.record_time) as max_time
from ${tableName} t
<if test="ew != null">
${ew.customSqlSegment}
</if>
group by t.device_id, time_slot
) t2 on t1.device_id = t2.device_id and t1.record_time = t2.max_time
order by t1.device_id asc, t1.record_time desc
</select>
<!-- 采样查询(多表):每 N 分钟取一条代表数据 -->
<select id="selectWithSamplingMultiTable" resultMap="RfidReadRecordResult">
select sampled.id,
sampled.device_id,
d.device_code as deviceCode,
d.device_name as deviceName,
sampled.read_status,
sampled.barcode,
sampled.record_time,
sampled.alarm_flag,
sampled.alarm_level,
sampled.alarm_type,
sampled.alarm_action
from (
<foreach collection="tableNames" item="tbl" separator=" UNION ALL ">
select t1.id,
t1.device_id,
t1.read_status,
t1.barcode,
t1.record_time,
t1.alarm_flag,
t1.alarm_level,
t1.alarm_type,
t1.alarm_action
from ${tbl} t1
inner join (
select
t.device_id,
<!-- 同上:将 record_time 映射到按采样间隔划分的时间槽,用于每个设备每个时间槽只保留一条最新记录 -->
FLOOR(UNIX_TIMESTAMP(t.record_time) / (#{samplingInterval} * 60)) as time_slot,
MAX(t.record_time) as max_time
from ${tbl} t
<if test="ew != null">
${ew.customSqlSegment}
</if>
group by t.device_id, time_slot
) t2 on t1.device_id = t2.device_id and t1.record_time = t2.max_time
</foreach>
) sampled
left join rfid_device d on sampled.device_id = d.id
order by sampled.device_id asc, sampled.record_time desc
</select>
</mapper>

Loading…
Cancel
Save