feat(rfid): 新增设备编号冗余字段与看板统计优化,添加查询范围限制

1. 新增deviceCode字段到实体、VO和BO,用于查询过滤和减少连表
2. 重构看板统计逻辑,改为单SQL聚合查询减少数据库扫描
3. 为读取记录查询添加日期范围限制,防止全表/全分表扫描
4. 优化设备查询条件构建,修复连表字段歧义问题
5. 新增分表检查并发锁,避免缓存失效时重复查询元数据
main
zch 1 month ago
parent 304ebfc172
commit f032eac098

@ -1,12 +1,9 @@
package org.dromara.rfid.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.rfid.domain.RfidDevice;
import org.dromara.rfid.domain.vo.RfidDashboardStatsVo;
import org.dromara.rfid.service.IRfidDeviceService;
import org.dromara.rfid.mapper.RfidDeviceMapper;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -24,42 +21,14 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/rfid/dashboard")
public class RfidDashboardController {
private final IRfidDeviceService rfidDeviceService;
/** 在线状态:在线 */
private static final String ONLINE_STATUS_ONLINE = "1";
/** 在线状态:离线 */
private static final String ONLINE_STATUS_OFFLINE = "0";
/** 告警状态:告警 */
private static final String ALARM_STATUS_ALARM = "1";
private final RfidDeviceMapper rfidDeviceMapper;
/**
*
*/
@GetMapping("/stats")
public R<RfidDashboardStatsVo> getStats() {
RfidDashboardStatsVo stats = new RfidDashboardStatsVo();
// 设备总数
stats.setTotalDevices(rfidDeviceService.count());
// 在线数量 (online_status = 1)
stats.setOnlineDevices(rfidDeviceService.count(
Wrappers.<RfidDevice>lambdaQuery().eq(RfidDevice::getOnlineStatus, ONLINE_STATUS_ONLINE)//在线状态(0-离线;1-在线)
));
// 离线数量 (online_status = 0)
stats.setOfflineDevices(rfidDeviceService.count(
Wrappers.<RfidDevice>lambdaQuery().eq(RfidDevice::getOnlineStatus, ONLINE_STATUS_OFFLINE)//在线状态(0-离线;1-在线)
));
// 告警数量 (alarm_status = 1)
stats.setAlarmDevices(rfidDeviceService.count(
Wrappers.<RfidDevice>lambdaQuery().eq(RfidDevice::getAlarmStatus, ALARM_STATUS_ALARM)//告警状态(0-正常;1-告警)
));
return R.ok(stats);
// 旧接口仍保持原有统计口径,但用一次聚合查询降低首页刷新时的数据库压力。
return R.ok(rfidDeviceMapper.selectDashboardStats());
}
}

@ -1,13 +1,13 @@
package org.dromara.rfid.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
import java.util.Date;
/**
*
@ -43,6 +43,11 @@ public class RfidReadRecord extends BaseEntity {
*/
private Long deviceId;
/**
*
*/
private String deviceCode;
/**
* (1-;0-)
*/

@ -37,6 +37,11 @@ public class RfidReadRecordBo extends BaseEntity {
@NotNull(message = "设备id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long deviceId;
/**
*
*/
private String deviceCode;
/**
* (1-;0-)
*/

@ -12,9 +12,6 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* rfid_read_record

@ -1,7 +1,6 @@
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;
@ -48,6 +47,14 @@ public class RfidReadRecordTableHelper {
*/
private static final Map<String, Boolean> TABLE_EXISTS_CACHE = new ConcurrentHashMap<>(64);
/**
*
* <p>
* information_schema
* </p>
*/
private static final Map<String, Object> TABLE_CHECK_LOCKS = new ConcurrentHashMap<>(64);
/**
*
*/
@ -72,6 +79,7 @@ public class RfidReadRecordTableHelper {
*/
public static void clearTableExistsCache() {
TABLE_EXISTS_CACHE.clear();
TABLE_CHECK_LOCKS.clear();
lastCacheRefreshTime = System.currentTimeMillis();
log.debug("分表缓存已清除");
}
@ -156,19 +164,6 @@ public class RfidReadRecordTableHelper {
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>
@ -192,15 +187,21 @@ public class RfidReadRecordTableHelper {
return cached;
}
// 查询数据库
boolean exists = doCheckTableExists(tableName);
Object tableLock = TABLE_CHECK_LOCKS.computeIfAbsent(tableName, key -> new Object());
synchronized (tableLock) {
// 获取锁后再次读取缓存,避免并发请求重复查询数据库元数据。
cached = TABLE_EXISTS_CACHE.get(tableName);
if (cached != null) {
return cached;
}
// 只缓存存在的表(不存在的表可能后续会创建)
if (exists) {
TABLE_EXISTS_CACHE.put(tableName, Boolean.TRUE);
boolean exists = doCheckTableExists(tableName);
// 只缓存存在的表(不存在的表可能后续会创建)
if (exists) {
TABLE_EXISTS_CACHE.put(tableName, Boolean.TRUE);
}
return exists;
}
return exists;
}
/**

@ -1,7 +1,9 @@
package org.dromara.rfid.mapper;
import org.dromara.rfid.domain.RfidDevice;
import org.dromara.rfid.domain.vo.DashboardVO;
import org.dromara.rfid.domain.vo.RfidDeviceVo;
import org.dromara.rfid.domain.vo.RfidDashboardStatsVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -50,6 +52,20 @@ public interface RfidDeviceMapper extends BaseMapperPlus<RfidDevice, RfidDeviceV
*/
Long countCustomRfidDevice(@Param(Constants.WRAPPER) Wrapper<RfidDevice> queryWrapper);
/**
*
*
* @return
*/
DashboardVO.StatisticsOverview selectMarkedDeviceStatistics();
/**
*
*
* @return
*/
RfidDashboardStatsVo selectDashboardStats();
/**
*
*

@ -71,32 +71,8 @@ public class DashboardServiceImpl implements IDashboardService {
*/
@Override
public DashboardVO.StatisticsOverview getOverview() {
DashboardVO.StatisticsOverview overview = new DashboardVO.StatisticsOverview();
// 统计设备总数
Long deviceTotal = deviceMapper.selectCount(Wrappers.lambdaQuery(RfidDevice.class)
.eq(RfidDevice::getIsMarked, "1"));
overview.setDeviceTotal(deviceTotal);
// 统计在线数量
Long onlineCount = deviceMapper.selectCount(Wrappers.lambdaQuery(RfidDevice.class)
.eq(RfidDevice::getIsMarked, "1")
.eq(RfidDevice::getOnlineStatus, "1"));
overview.setOnlineCount(onlineCount);
// 统计离线数量
Long offlineCount = deviceMapper.selectCount(Wrappers.lambdaQuery(RfidDevice.class)
.eq(RfidDevice::getIsMarked, "1")
.eq(RfidDevice::getOnlineStatus, "0"));
overview.setOfflineCount(offlineCount);
// 统计告警数量(当天告警设备数)
Long alarmCount = deviceMapper.selectCount(Wrappers.lambdaQuery(RfidDevice.class)
.eq(RfidDevice::getIsMarked, "1")
.eq(RfidDevice::getAlarmStatus, "1"));
overview.setAlarmCount(alarmCount);
return overview;
// 看板按秒级刷新,统计口径固定为“已标识设备”,一次聚合可减少数据库重复扫描。
return deviceMapper.selectMarkedDeviceStatistics();
}
/**

@ -6,7 +6,9 @@ import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -23,7 +25,6 @@ import org.dromara.rfid.helper.RfidReadRecordTableHelper;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
@ -61,7 +62,7 @@ public class RfidDeviceServiceImpl implements IRfidDeviceService {
*/
@Override
public TableDataInfo<RfidDeviceVo> queryPageList(RfidDeviceBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<RfidDevice> lqw = buildQueryWrapper(bo);
Wrapper<RfidDevice> lqw = buildQueryWrapper(bo);
// 使用自定义 Mapper XML + MyBatis-Plus Wrapper 进行分页查询
Page<RfidDeviceVo> result = baseMapper.selectCustomRfidDeviceVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
@ -75,7 +76,7 @@ public class RfidDeviceServiceImpl implements IRfidDeviceService {
*/
@Override
public List<RfidDeviceVo> queryList(RfidDeviceBo bo) {
LambdaQueryWrapper<RfidDevice> lqw = buildQueryWrapper(bo);
Wrapper<RfidDevice> lqw = buildQueryWrapper(bo);
// 使用自定义 Mapper XML + MyBatis-Plus Wrapper 查询列表
return baseMapper.selectCustomRfidDeviceVoList(lqw);
}
@ -94,28 +95,50 @@ public class RfidDeviceServiceImpl implements IRfidDeviceService {
return baseMapper.countCustomRfidDevice(wrapper);
}
private LambdaQueryWrapper<RfidDevice> buildQueryWrapper(RfidDeviceBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<RfidDevice> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(RfidDevice::getId);
lqw.eq(StringUtils.isNotBlank(bo.getDeviceCode()), RfidDevice::getDeviceCode, bo.getDeviceCode());
lqw.like(StringUtils.isNotBlank(bo.getDeviceName()), RfidDevice::getDeviceName, bo.getDeviceName());
lqw.eq(bo.getLocationId() != null, RfidDevice::getLocationId, bo.getLocationId());
lqw.eq(StringUtils.isNotBlank(bo.getDeviceAddress()), RfidDevice::getDeviceAddress, bo.getDeviceAddress());
lqw.eq(bo.getDevicePort() != null, RfidDevice::getDevicePort, bo.getDevicePort());
lqw.eq(bo.getReadFrequency() != null, RfidDevice::getReadFrequency, bo.getReadFrequency());
lqw.eq(StringUtils.isNotBlank(bo.getOnlineStatus()), RfidDevice::getOnlineStatus, bo.getOnlineStatus());
lqw.eq(StringUtils.isNotBlank(bo.getAlarmStatus()), RfidDevice::getAlarmStatus, bo.getAlarmStatus());
if (StringUtils.isNotBlank(bo.getIsMarked())) {
lqw.apply("t.is_marked = {0}", bo.getIsMarked());
}
lqw.eq(StringUtils.isNotBlank(bo.getCreatedBy()), RfidDevice::getCreatedBy, bo.getCreatedBy());
lqw.eq(bo.getCreatedAt() != null, RfidDevice::getCreatedAt, bo.getCreatedAt());
lqw.eq(StringUtils.isNotBlank(bo.getUpdatedBy()), RfidDevice::getUpdatedBy, bo.getUpdatedBy());
lqw.eq(bo.getUpdatedAt() != null, RfidDevice::getUpdatedAt, bo.getUpdatedAt());
private Wrapper<RfidDevice> buildQueryWrapper(RfidDeviceBo bo) {
QueryWrapper<RfidDevice> lqw = Wrappers.query();
lqw.orderByAsc("t.id");
lqw.eq(StringUtils.isNotBlank(bo.getDeviceCode()), "t.device_code", bo.getDeviceCode());
lqw.like(StringUtils.isNotBlank(bo.getDeviceName()), "t.device_name", bo.getDeviceName());
appendLocationScopeCondition(lqw, bo.getLocationId());
lqw.eq(StringUtils.isNotBlank(bo.getDeviceAddress()), "t.device_address", bo.getDeviceAddress());
lqw.eq(bo.getDevicePort() != null, "t.device_port", bo.getDevicePort());
lqw.eq(bo.getReadFrequency() != null, "t.read_frequency", bo.getReadFrequency());
lqw.eq(StringUtils.isNotBlank(bo.getOnlineStatus()), "t.online_status", bo.getOnlineStatus());
lqw.eq(StringUtils.isNotBlank(bo.getAlarmStatus()), "t.alarm_status", bo.getAlarmStatus());
// 设备表与位置表都有 is_marked 字段,显式指定 t 别名,避免连表查询出现歧义。
lqw.eq(StringUtils.isNotBlank(bo.getIsMarked()), "t.is_marked", bo.getIsMarked());
lqw.eq(StringUtils.isNotBlank(bo.getCreatedBy()), "t.created_by", bo.getCreatedBy());
lqw.eq(bo.getCreatedAt() != null, "t.created_at", bo.getCreatedAt());
lqw.eq(StringUtils.isNotBlank(bo.getUpdatedBy()), "t.updated_by", bo.getUpdatedBy());
lqw.eq(bo.getUpdatedAt() != null, "t.updated_at", bo.getUpdatedAt());
return lqw;
}
/**
*
* <p>
* 3- 1- 2-
* id
*/
private void appendLocationScopeCondition(QueryWrapper<RfidDevice> lqw, Long locationId) {
if (locationId == null) {
return;
}
lqw.and(wrapper -> wrapper
.eq("t.location_id", locationId)
.or()
.apply("""
exists (
select 1
from rfid_location leaf
where leaf.id = t.location_id
and leaf.location_type = '3'
and find_in_set({0}, leaf.ancestors) > 0
)
""", locationId));
}
/**
*
*
@ -153,14 +176,14 @@ public class RfidDeviceServiceImpl implements IRfidDeviceService {
*/
private void validEntityBeforeSave(RfidDevice entity){
// 业务编号 deviceCode 唯一校验
// if (StringUtils.isNotBlank(entity.getDeviceCode())) {
// boolean exists = baseMapper.existsRfidDevice(Wrappers.<RfidDevice>lambdaQuery()
// .eq(RfidDevice::getDeviceCode, entity.getDeviceCode())
// .ne(entity.getId() != null, RfidDevice::getId, entity.getId()));
// if (exists) {
// throw new ServiceException("设备编号已存在");
// }
// }
if (StringUtils.isNotBlank(entity.getDeviceCode())) {
boolean exists = baseMapper.existsRfidDevice(Wrappers.<RfidDevice>lambdaQuery()
.eq(RfidDevice::getDeviceCode, entity.getDeviceCode())
.ne(entity.getId() != null, RfidDevice::getId, entity.getId()));
if (exists) {
throw new ServiceException("设备编号已存在");
}
}
// IP地址 deviceAddress 唯一校验
if (StringUtils.isNotBlank(entity.getDeviceAddress())) {

@ -21,6 +21,8 @@ import org.dromara.rfid.domain.RfidReadRecord;
import org.dromara.rfid.mapper.RfidReadRecordMapper;
import org.dromara.rfid.service.IRfidReadRecordService;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Collection;
@ -48,6 +50,11 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService {
*/
private static final Pattern BARCODE_CLEAN_PATTERN = Pattern.compile("[\\s\\x00-\\x1F\\x7F]+");
/**
* UNION
*/
private static final long MAX_QUERY_DAYS = 30L;
/**
*
*
@ -77,6 +84,8 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService {
*/
@Override
public TableDataInfo<RfidReadRecordVo> queryPageList(RfidReadRecordBo bo, PageQuery pageQuery) {
assertQueryDateRange(bo);
// 获取实际存在的分表列表
List<String> tableNames = RfidReadRecordTableHelper.getExistingTableNames(
bo.getBeginRecordTime(), bo.getEndRecordTime());
@ -109,6 +118,8 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService {
*/
@Override
public List<RfidReadRecordVo> queryList(RfidReadRecordBo bo) {
assertQueryDateRange(bo);
// 获取实际存在的分表列表
List<String> tableNames = RfidReadRecordTableHelper.getExistingTableNames(
bo.getBeginRecordTime(), bo.getEndRecordTime());
@ -171,6 +182,36 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService {
return lqw;
}
/**
*
* <p>
* /
* </p>
*/
private void assertQueryDateRange(RfidReadRecordBo bo) {
Date beginRecordTime = bo.getBeginRecordTime();
Date endRecordTime = bo.getEndRecordTime();
if (beginRecordTime == null && endRecordTime == null) {
return;
}
LocalDate start = beginRecordTime != null
? cn.hutool.core.date.DateUtil.toLocalDateTime(beginRecordTime).toLocalDate()
: LocalDate.now();
LocalDate end = endRecordTime != null
? cn.hutool.core.date.DateUtil.toLocalDateTime(endRecordTime).toLocalDate()
: LocalDate.now();
if (start.isAfter(end)) {
LocalDate temp = start;
start = end;
end = temp;
}
long queryDays = ChronoUnit.DAYS.between(start, end) + 1;
if (queryDays > MAX_QUERY_DAYS) {
throw new ServiceException("读取记录查询范围不能超过" + MAX_QUERY_DAYS + "天");
}
}
/**
*
*
@ -204,20 +245,6 @@ public class RfidReadRecordServiceImpl implements IRfidReadRecordService {
record.setBarcode(cleanedBarcode);
}
/**
*
*/
private String toHexString(String str) {
if (str == null) {
return "null";
}
StringBuilder sb = new StringBuilder();
for (char c : str.toCharArray()) {
sb.append(String.format("%02X ", (int) c));
}
return sb.toString().trim();
}
/**
*
* <p>

Loading…
Cancel
Save