feat(dashboard): 新增RFID监控看板模块及相关服务接口

- 添加DashboardController,提供实时统计、设备状态和成功率趋势三个独立接口
- 实现DashboardService,实现看板数据的获取和数据组装逻辑
- 新增DashboardVO,定义看板相关数据传输对象结构
- RfidDeviceServiceImpl中删除操作增加关联读取记录校验,防止孤儿记录产生
- RfidLocationServiceImpl中删除操作增加子节点及绑定设备校验,避免非法删除
- 优化RfidLocationService中子节点ancestors更新为批量SQL操作,提高性能
- RfidLocationMapper新增批量更新子节点ancestors方法及对应XML配置
- 引入事务管理保证新增、更新和删除操作的原子性及异常回滚
- 设备状态接口返回含设备信息的完整位置树,便于前端设备树构建与展示
main
zangch@mesnac.com 4 months ago
parent f048869aa9
commit 47b7112c50

@ -0,0 +1,132 @@
package org.dromara.rfid.controller;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.web.core.BaseController;
import org.dromara.rfid.domain.vo.DashboardVO;
import org.dromara.rfid.service.IDashboardService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* RFID
* <p>
*
* <ul>
* <li> + </li>
* <li> + </li>
* <li>线</li>
* </ul>
* </p>
*
* @author zch
* @date 2025-11-26
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/rfid/dashboard")
public class DashboardController extends BaseController {
private final IDashboardService dashboardService;
/**
* 1
* <p>
* 线线+
* </p>
* <p>
*
* </p>
*
* @param alarmLimit
* @return
*/
@GetMapping("/realtime")
public R<DashboardVO.RealtimeStats> getRealtimeStats(
@RequestParam(required = false) Integer alarmLimit) {
return R.ok(dashboardService.getRealtimeStats(alarmLimit));
}
/**
* 2
* <p>
*
* </p>
* <p>
*
* <ul>
* <li></li>
* <li> 3ID线</li>
* <li> C# WebSocket </li>
* <li> deviceId WebSocket </li>
* </ul>
* </p>
*
* @return
*/
@GetMapping("/deviceStatus")
public R<List<DashboardVO.LocationTreeNode>> getLocationTree() {
return R.ok(dashboardService.getLocationTree());
}
/**
* 3
* <p>
* 线
*
* <ul>
* <li></li>
* <li>0-23 </li>
* <li></li>
* </ul>
* </p>
*
* @param type
* @return 24
*/
@GetMapping("/successRate")
public R<List<DashboardVO.SuccessRateTrend>> getSuccessRateTrends(
@RequestParam(required = false, defaultValue = "today") String type) {
return R.ok(dashboardService.getSuccessRateTrends(type));
}
// ==================== 以下为原有接口(保留) ====================
/**
*
*
* @param locationId ID
* @return
*/
@GetMapping("/data")
public R<DashboardVO> getDashboardData(
@RequestParam(required = false) Long locationId) {
return R.ok(dashboardService.getDashboardData(locationId));
}
/**
*
*
* @return
*/
@GetMapping("/overview")
public R<DashboardVO.StatisticsOverview> getOverview() {
return R.ok(dashboardService.getOverview());
}
/**
*
*
* @param limit 10
* @return
*/
@GetMapping("/alarmStats")
public R<List<DashboardVO.AlarmStatVO>> getAlarmStats(
@RequestParam(required = false, defaultValue = "10") Integer limit) {
return R.ok(dashboardService.getAlarmStats(limit));
}
}

@ -0,0 +1,258 @@
package org.dromara.rfid.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* VO
*
* @author zch
* @date 2025-11-26
*/
@Data
public class DashboardVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
// ==================== 顶层属性(用于 getDashboardData 返回) ====================
/**
*
*/
private StatisticsOverview overview;
/**
*
*/
private List<LocationTreeNode> locationTree;
/**
*
*/
private List<SuccessRateTrend> successRateTrends;
/**
*
*/
private List<AlarmStatVO> alarmStats;
// ==================== 内部类定义 ====================
/**
*
* <p>
* +
* </p>
*/
@Data
public static class RealtimeStats implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
private StatisticsOverview overview;
/**
*
*/
private List<AlarmStatVO> alarmStats;
}
/**
*
*/
@Data
public static class StatisticsOverview implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
private Long deviceTotal;
/**
* 线
*/
private Long onlineCount;
/**
* 线
*/
private Long offlineCount;
/**
*
*/
private Long alarmCount;
}
/**
*
* <p>
* locationType = 3
* deviceId WebSocket
* </p>
*/
@Data
public static class LocationTreeNode implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
// ==================== 位置信息 ====================
/**
* ID
*/
private Long id;
/**
*
*/
private String locationCode;
/**
*
*/
private String locationAlias;
/**
* (1-; 2-; 3-/)
*/
private String locationType;
/**
* ID
*/
private Long parentId;
// ==================== 设备信息(仅当 locationType=3 时有值) ====================
/**
* ID WebSocket
*/
private Long deviceId;
/**
*
*/
private String deviceCode;
/**
*
*/
private String deviceName;
/**
* 线 (1-线; 0-线)
*/
private String onlineStatus;
/**
* (0-; 1-)
*/
private String alarmStatus;
// ==================== 子节点 ====================
/**
*
*/
private List<LocationTreeNode> children;
}
// ==================== 以下为已废弃的实体类,保留以备参考 ====================
/*
* VO LocationTreeNode
*
@Data
public static class DeviceStatusVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long deviceId;
private String deviceCode;
private String deviceName;
private Long locationId;
private String locationAlias;
private String onlineStatus;
private String alarmStatus;
private LatestReadRecord latestRecord;
}
*/
/*
* WebSocket
*
@Data
public static class LatestReadRecord implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String stationName;
private String barcode;
private String recordTime;
private String readStatus;
}
*/
/**
*
*/
@Data
public static class SuccessRateTrend implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* "09:00"
*/
private String timePoint;
/**
* 98.5
*/
private Double successRate;
private Double yesterdaySuccessRate;
}
/**
*
*/
@Data
public static class AlarmStatVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
private String alarmTime;
/**
*
*/
private String deviceName;
/**
*
*/
private String location;
/**
*
*/
private String alarmLevel;
/**
*
*/
private String alarmAction;
}
}

@ -100,5 +100,16 @@ public interface RfidLocationMapper extends BaseMapperPlus<RfidLocation, RfidLoc
*/
Boolean existsRfidLocation(@Param(Constants.WRAPPER) Wrapper<RfidLocation> queryWrapper);
/**
* ancestors
*
* @param parentId ID
* @param newAncestors
* @param oldAncestors
* @return
*/
int updateChildrenAncestors(@Param("parentId") Long parentId,
@Param("newAncestors") String newAncestors,
@Param("oldAncestors") String oldAncestors);
}

@ -0,0 +1,81 @@
package org.dromara.rfid.service;
import org.dromara.rfid.domain.vo.DashboardVO;
import java.util.List;
/**
*
* <p>
*
* <ul>
* <li> + </li>
* <li> + </li>
* <li>线</li>
* </ul>
* </p>
*
* @author zch
* @date 2025-11-26
*/
public interface IDashboardService {
/**
* 1
* <p>
* 线线+
* </p>
*
* @param alarmLimit
* @return
*/
DashboardVO.RealtimeStats getRealtimeStats(Integer alarmLimit);
/**
* 2
* <p>
*
* 3
* WebSocket
* </p>
*
* @return
*/
List<DashboardVO.LocationTreeNode> getLocationTree();
/**
* 3
* <p>
* +
* </p>
*
* @param type
* @return
*/
List<DashboardVO.SuccessRateTrend> getSuccessRateTrends(String type);
// ==================== 以下为原有接口(保留) ====================
/**
*
*
* @return
*/
DashboardVO.StatisticsOverview getOverview();
/**
*
*
* @param limit
* @return
*/
List<DashboardVO.AlarmStatVO> getAlarmStats(Integer limit);
/**
*
*
* @param locationId ID
* @return
*/
DashboardVO getDashboardData(Long locationId);
}

@ -0,0 +1,287 @@
package org.dromara.rfid.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.rfid.domain.RfidDevice;
import org.dromara.rfid.domain.RfidLocation;
import org.dromara.rfid.domain.vo.DashboardVO;
import org.dromara.rfid.domain.vo.RfidDeviceVo;
import org.dromara.rfid.domain.vo.RfidLocationVo;
import org.dromara.rfid.domain.vo.RfidReadRecordVo;
import org.dromara.rfid.helper.RfidReadRecordTableHelper;
import org.dromara.rfid.mapper.RfidDeviceMapper;
import org.dromara.rfid.mapper.RfidLocationMapper;
import org.dromara.rfid.mapper.RfidReadRecordMapper;
import org.dromara.rfid.service.IDashboardService;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
*
* <p>
*
* <ul>
* <li> + </li>
* <li> + </li>
* <li>线</li>
* </ul>
* </p>
*
* @author zch
* @date 2025-11-26
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class DashboardServiceImpl implements IDashboardService {
private final RfidDeviceMapper deviceMapper;
private final RfidLocationMapper locationMapper;
private final RfidReadRecordMapper readRecordMapper;
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 1
*/
@Override
public DashboardVO.RealtimeStats getRealtimeStats(Integer alarmLimit) {
DashboardVO.RealtimeStats realtimeStats = new DashboardVO.RealtimeStats();
// 获取统计概览
realtimeStats.setOverview(getOverview());
// 获取告警统计列表
realtimeStats.setAlarmStats(getAlarmStats(alarmLimit));
return realtimeStats;
}
/**
*
*/
@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;
}
/**
*
* <p>
* 3
* WebSocket
* </p>
*/
@Override
public List<DashboardVO.LocationTreeNode> getLocationTree() {
// 1. 查询所有已标识的位置
QueryWrapper<RfidLocation> locationWrapper = Wrappers.query(RfidLocation.class);
locationWrapper.eq("t.is_marked", "1");
locationWrapper.orderByAsc("t.parent_id", "t.id");
List<RfidLocationVo> locations = locationMapper.selectCustomRfidLocationVoList(locationWrapper);
if (CollUtil.isEmpty(locations)) {
return Collections.emptyList();
}
// 2. 查询所有已标识的设备,构建 locationId -> 设备列表 的映射
QueryWrapper<RfidDevice> deviceWrapper = Wrappers.query(RfidDevice.class);
deviceWrapper.eq("t.is_marked", "1");
List<RfidDeviceVo> devices = deviceMapper.selectCustomRfidDeviceVoList(deviceWrapper);
Map<Long, List<RfidDeviceVo>> devicesByLocation = devices.stream()
.filter(d -> d.getLocationId() != null)
.collect(Collectors.groupingBy(RfidDeviceVo::getLocationId));
// 3. 构建位置树节点
List<DashboardVO.LocationTreeNode> allNodes = new ArrayList<>();
Map<Long, DashboardVO.LocationTreeNode> nodeMap = new HashMap<>();
for (RfidLocationVo loc : locations) {
DashboardVO.LocationTreeNode node = new DashboardVO.LocationTreeNode();
node.setId(loc.getId());
node.setLocationCode(loc.getLocationCode());
node.setLocationAlias(loc.getLocationAlias());
node.setLocationType(loc.getLocationType());
node.setParentId(loc.getParentId());
node.setChildren(new ArrayList<>());
// 如果是工位locationType = 3关联设备信息
if ("3".equals(loc.getLocationType())) {
List<RfidDeviceVo> locationDevices = devicesByLocation.get(loc.getId());
if (CollUtil.isNotEmpty(locationDevices)) {
// 取第一个设备(通常一个工位对应一个设备)
RfidDeviceVo device = locationDevices.get(0);
node.setDeviceId(device.getId());
node.setDeviceCode(device.getDeviceCode());
node.setDeviceName(device.getDeviceName());
node.setOnlineStatus(device.getOnlineStatus());
node.setAlarmStatus(device.getAlarmStatus());
}
}
allNodes.add(node);
nodeMap.put(loc.getId(), node);
}
// 4. 构建树形结构
List<DashboardVO.LocationTreeNode> rootNodes = new ArrayList<>();
for (DashboardVO.LocationTreeNode node : allNodes) {
Long parentId = node.getParentId();
if (parentId == null || parentId == 0L) {
// 顶级节点
rootNodes.add(node);
} else {
// 挂载到父节点
DashboardVO.LocationTreeNode parent = nodeMap.get(parentId);
if (parent != null) {
parent.getChildren().add(node);
} else {
// 父节点不存在,作为顶级节点处理
rootNodes.add(node);
}
}
}
return rootNodes;
}
/**
*
* <p>
* /线
* 0-23 24 使 null
* </p>
*
* @param type today-yesterday-
* @return 24
*/
@Override
public List<DashboardVO.SuccessRateTrend> getSuccessRateTrends(String type) {
LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);
Map<String, Double> todayRateMap = buildSuccessRateMap(today);
Map<String, Double> yesterdayRateMap = buildSuccessRateMap(yesterday);
List<DashboardVO.SuccessRateTrend> trends = new ArrayList<>();
for (int hour = 0; hour < 24; hour++) {
String timePoint = String.format("%02d:00", hour);
DashboardVO.SuccessRateTrend trend = new DashboardVO.SuccessRateTrend();
trend.setTimePoint(timePoint);
trend.setSuccessRate(todayRateMap.getOrDefault(timePoint, null));
trend.setYesterdaySuccessRate(yesterdayRateMap.getOrDefault(timePoint, null));
trends.add(trend);
}
return trends;
}
private Map<String, Double> buildSuccessRateMap(LocalDate targetDate) {
String tableName = RfidReadRecordTableHelper.getTableName(targetDate);
String startTime = targetDate.atStartOfDay().format(TIME_FORMATTER);
String endTime = targetDate.atTime(23, 59, 59).format(TIME_FORMATTER);
List<Map<String, Object>> results;
try {
results = readRecordMapper.selectSuccessRateByHour(tableName, startTime, endTime);
} catch (Exception e) {
log.warn("查询成功率趋势失败,表可能不存在: {}", tableName);
results = Collections.emptyList();
}
Map<String, Double> rateMap = new HashMap<>();
if (CollUtil.isNotEmpty(results)) {
for (Map<String, Object> row : results) {
String timePoint = (String) row.get("timePoint");
if (timePoint == null) {
continue;
}
Object rateObj = row.get("successRate");
Double rate = rateObj != null ? Double.parseDouble(rateObj.toString()) : null;
rateMap.put(timePoint, rate);
}
}
return rateMap;
}
/**
*
* <p>
* limit null
* </p>
*/
@Override
public List<DashboardVO.AlarmStatVO> getAlarmStats(Integer limit) {
// limit 为 null 时不限制数量
String tableName = RfidReadRecordTableHelper.getTodayTableName();
List<RfidReadRecordVo> alarmRecords;
try {
alarmRecords = readRecordMapper.selectAlarmRecordList(tableName, limit);
} catch (Exception e) {
log.warn("查询告警记录失败,表可能不存在: {}", tableName);
alarmRecords = Collections.emptyList();
}
return alarmRecords.stream().map(record -> {
DashboardVO.AlarmStatVO alarmStat = new DashboardVO.AlarmStatVO();
alarmStat.setAlarmTime(record.getRecordTime() != null
? DateUtil.format(record.getRecordTime(), "MM-dd HH:mm")
: null);
alarmStat.setDeviceName(record.getDeviceName());
alarmStat.setLocation(record.getLocationAlias());
alarmStat.setAlarmLevel(record.getAlarmLevel());
alarmStat.setAlarmAction(record.getAlarmAction());
return alarmStat;
}).collect(Collectors.toList());
}
/**
*
*/
@Override
public DashboardVO getDashboardData(Long locationId) {
DashboardVO dashboard = new DashboardVO();
dashboard.setOverview(getOverview());
dashboard.setLocationTree(getLocationTree());
dashboard.setSuccessRateTrends(getSuccessRateTrends("today"));
dashboard.setAlarmStats(getAlarmStats(null));
return dashboard;
}
}

@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.rfid.domain.bo.RfidDeviceBo;
import org.dromara.rfid.domain.vo.RfidDeviceVo;
import org.dromara.rfid.domain.RfidDevice;
@ -18,7 +19,9 @@ import org.dromara.rfid.domain.RfidReadRecord;
import org.dromara.rfid.mapper.RfidDeviceMapper;
import org.dromara.rfid.mapper.RfidReadRecordMapper;
import org.dromara.rfid.service.IRfidDeviceService;
import org.dromara.rfid.helper.RfidReadRecordTableHelper;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Collection;
@ -120,6 +123,7 @@ public class RfidDeviceServiceImpl implements IRfidDeviceService {
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(RfidDeviceBo bo) {
RfidDevice add = MapstructUtils.convert(bo, RfidDevice.class);
validEntityBeforeSave(add);
@ -137,6 +141,7 @@ public class RfidDeviceServiceImpl implements IRfidDeviceService {
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(RfidDeviceBo bo) {
RfidDevice update = MapstructUtils.convert(bo, RfidDevice.class);
validEntityBeforeSave(update);
@ -171,14 +176,23 @@ public class RfidDeviceServiceImpl implements IRfidDeviceService {
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid && ids != null && !ids.isEmpty()) {
// 校验是否存在关联的读取记录,防止产生孤儿记录
boolean existsRecord = rfidReadRecordMapper.existsRfidReadRecord(
Wrappers.<RfidReadRecord>lambdaQuery().in(RfidReadRecord::getDeviceId, ids)
);
if (existsRecord) {
throw new ServiceException("存在关联读取记录的设备,无法删除");
// 获取最近30天实际存在的分表进行检查
Date endDate = new Date();
Date startDate = cn.hutool.core.date.DateUtil.offsetDay(endDate, -30);
List<String> tableNames = RfidReadRecordTableHelper.getExistingTableNames(startDate, endDate);
// 只有存在分表时才进行检查
if (!tableNames.isEmpty()) {
boolean existsRecord = rfidReadRecordMapper.existsRfidReadRecordMultiTable(
tableNames,
Wrappers.<RfidReadRecord>lambdaQuery().in(RfidReadRecord::getDeviceId, ids)
);
if (existsRecord) {
throw new ServiceException("存在关联读取记录的设备,无法删除");
}
}
}
return baseMapper.deleteByIds(ids) > 0;

@ -7,12 +7,15 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.rfid.domain.RfidDevice;
import org.dromara.rfid.domain.RfidLocation;
import org.dromara.rfid.domain.bo.RfidLocationBo;
import org.dromara.rfid.domain.vo.RfidLocationVo;
import org.dromara.rfid.mapper.RfidDeviceMapper;
import org.dromara.rfid.mapper.RfidLocationMapper;
import org.dromara.rfid.service.IRfidLocationService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
@ -31,6 +34,8 @@ public class RfidLocationServiceImpl implements IRfidLocationService {
private final RfidLocationMapper baseMapper;
private final RfidDeviceMapper rfidDeviceMapper;
/**
*
*
@ -79,6 +84,7 @@ public class RfidLocationServiceImpl implements IRfidLocationService {
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(RfidLocationBo bo) {
RfidLocation add = MapstructUtils.convert(bo, RfidLocation.class);
validEntityBeforeSave(add);
@ -110,6 +116,7 @@ public class RfidLocationServiceImpl implements IRfidLocationService {
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(RfidLocationBo bo) {
RfidLocation update = MapstructUtils.convert(bo, RfidLocation.class);
validEntityBeforeSave(update);
@ -167,21 +174,11 @@ public class RfidLocationServiceImpl implements IRfidLocationService {
}
/**
* ancestors
* ancestors SQL
*/
private void updateChildrenAncestors(Long currentId, String newAncestors, String oldAncestors) {
// 查出所有以旧ancestors开头且包含当前id的子节点 (简单起见,这里仅演示逻辑,实际海量数据需优化)
// RuoYi标准做法通常是查出所有children然后替换前缀
// 这里使用数据库函数或全量更新
// 也可以查出所有直属子节点递归更新
List<RfidLocation> children = baseMapper.selectList(new LambdaQueryWrapper<RfidLocation>()
.apply("find_in_set({0}, ancestors)", currentId));
for (RfidLocation child : children) {
child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors));
baseMapper.updateById(child);
}
// 使用单条 SQL 批量更新所有后代节点的 ancestors避免逐条更新
baseMapper.updateChildrenAncestors(currentId, newAncestors, oldAncestors);
}
/**
@ -207,12 +204,21 @@ public class RfidLocationServiceImpl implements IRfidLocationService {
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
for (Long id : ids) {
if (baseMapper.exists(new LambdaQueryWrapper<RfidLocation>().eq(RfidLocation::getParentId, id))) {
throw new ServiceException("存在子节点,无法删除");
}
// 校验是否存在绑定设备,防止删除仍被设备引用的位置
boolean existsDevice = rfidDeviceMapper.existsRfidDevice(
Wrappers.<RfidDevice>lambdaQuery().eq(RfidDevice::getLocationId, id)
);
if (existsDevice) {
throw new ServiceException("存在绑定设备,无法删除");
}
}
}
return baseMapper.deleteByIds(ids) > 0;

@ -165,5 +165,11 @@
</if>
</select>
<!-- 批量更新子节点的 ancestors用于父节点变更时一次性更新所有后代节点 -->
<update id="updateChildrenAncestors">
update rfid_location
set ancestors = REPLACE(ancestors, #{oldAncestors}, #{newAncestors})
where find_in_set(#{parentId}, ancestors)
</update>
</mapper>

Loading…
Cancel
Save