Compare commits
5 Commits
main
...
8c636d0484
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c636d0484 | |||
| 0c474ae1f3 | |||
| b85664900e | |||
| 035b24fedd | |||
| e920cfb860 |
@ -1,8 +1,13 @@
|
||||
package com.fuyuanshen.global.mqtt.rule.xinghan;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fuyuanshen.common.core.constant.GlobalConstants;
|
||||
import com.fuyuanshen.common.core.utils.StringUtils;
|
||||
@ -10,11 +15,20 @@ import com.fuyuanshen.common.core.utils.date.DurationUtils;
|
||||
import com.fuyuanshen.common.json.utils.JsonUtils;
|
||||
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
||||
import com.fuyuanshen.equipment.domain.Device;
|
||||
import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord;
|
||||
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
|
||||
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
|
||||
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
|
||||
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
||||
import com.fuyuanshen.equipment.mapper.DeviceAlarmMapper;
|
||||
import com.fuyuanshen.equipment.mapper.DeviceFenceAccessRecordMapper;
|
||||
import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper;
|
||||
import com.fuyuanshen.equipment.service.DeviceService;
|
||||
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
|
||||
import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService;
|
||||
import com.fuyuanshen.equipment.service.IDeviceGeoFenceService;
|
||||
import com.fuyuanshen.equipment.utils.map.AmapTrackUtil;
|
||||
import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil;
|
||||
import com.fuyuanshen.equipment.utils.map.LngLonUtil;
|
||||
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
|
||||
@ -30,13 +44,16 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
|
||||
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
|
||||
@ -72,6 +89,12 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
||||
private final IDeviceAlarmService deviceAlarmService;
|
||||
private final DeviceService deviceService;
|
||||
private final DeviceAlarmMapper deviceAlarmMapper;
|
||||
private final AmapTrackUtil amapTrackUtil;
|
||||
private final IDeviceFenceAccessRecordService deviceFenceAccessRecordService;
|
||||
private final DeviceFenceAccessRecordMapper deviceFenceAccessRecordMapper;
|
||||
private final DeviceGeoFenceMapper deviceGeoFenceMapper;
|
||||
/** 位置未发生明显变化的距离阈值(米),可通过配置中心动态调整 */
|
||||
private final double MOVEMENT_THRESHOLD_METER = 10.0;
|
||||
|
||||
@Override
|
||||
public void execute(MqttRuleContext context) {
|
||||
@ -127,6 +150,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
||||
* 入口:保存报警(SOS 与 Shake 完全独立)
|
||||
*/
|
||||
public void saveAlarm(String deviceImei, MqttXinghanJson status) {
|
||||
try {
|
||||
int sos = Optional.ofNullable(status.getStaSOSGrade()).orElse(0);
|
||||
// 1. 处理 SOS 报警
|
||||
handleSingleAlarm(deviceImei,
|
||||
@ -137,6 +161,9 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
||||
handleSingleAlarm(deviceImei,
|
||||
shake > 0,
|
||||
AlarmTypeEnum.SHAKE);
|
||||
} catch (Exception e) {
|
||||
log.error("异步保存报警(SOS 与 Shake 完全独立)报错: device={}, error={}", deviceImei, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -236,7 +263,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
||||
String location = RedisUtils.getCacheObject(
|
||||
GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX);
|
||||
if (StrUtil.isNotBlank(location)) {
|
||||
bo.setLocation(JSONObject.parseObject(location).getString("address"));
|
||||
bo.setLocation(com.alibaba.fastjson2.JSONObject.parseObject(location).getString("address"));
|
||||
}
|
||||
return bo;
|
||||
}
|
||||
@ -259,63 +286,312 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
||||
public void asyncSendLocationToRedisWithFuture(String deviceImei, String latitude, String longitude) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
if(StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)){
|
||||
if (StringUtils.isAnyBlank(deviceImei, latitude, longitude)) {
|
||||
log.warn("位置上报参数为空,deviceImei={}", deviceImei);
|
||||
return;
|
||||
}
|
||||
String[] latArr = latitude.split("\\.");
|
||||
String[] lonArr = longitude.split("\\.");
|
||||
// 将位置信息存储到Redis中
|
||||
//log.info("位置上报,deviceImei={}, lat={}, lon={}", deviceImei, latitude, longitude);
|
||||
// 1. 解析当前上报的经纬度
|
||||
Double curLat = parseDoubleSafe(latitude.trim());
|
||||
Double curLon = parseDoubleSafe(longitude.trim());
|
||||
if (curLat == null || curLon == null) {
|
||||
log.warn("经纬度格式错误,直接更新,deviceImei={}, lat={}, lon={}", deviceImei, latitude, longitude);
|
||||
doSaveLocation(deviceImei, latitude, longitude);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 读取 Redis 中缓存的上一次位置
|
||||
String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX;
|
||||
String redisObj = RedisUtils.getCacheObject(redisKey);
|
||||
JSONObject jsonOBj = JSONObject.parseObject(redisObj);
|
||||
if(jsonOBj != null){
|
||||
String str1 = latArr[0] +"."+ latArr[1].substring(0,4);
|
||||
String str2 = lonArr[0] +"."+ lonArr[1].substring(0,4);
|
||||
String cachedJson = RedisUtils.getCacheObject(redisKey);
|
||||
|
||||
String cacheLatitude = jsonOBj.getString("wgs84_latitude");
|
||||
String cacheLongitude = jsonOBj.getString("wgs84_longitude");
|
||||
String[] latArr1 = cacheLatitude.split("\\.");
|
||||
String[] lonArr1 = cacheLongitude.split("\\.");
|
||||
if (StringUtils.isNotBlank(cachedJson)) {
|
||||
com.alibaba.fastjson2.JSONObject cachedObj = com.alibaba.fastjson2.JSONObject.parseObject(cachedJson);
|
||||
String cachedWgs84Lat = cachedObj.getString("wgs84_latitude");
|
||||
String cachedWgs84Lon = cachedObj.getString("wgs84_longitude");
|
||||
|
||||
String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4);
|
||||
String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4);
|
||||
if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){
|
||||
log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
|
||||
Double oldLat = parseDoubleSafe(cachedWgs84Lat);
|
||||
Double oldLon = parseDoubleSafe(cachedWgs84Lon);
|
||||
|
||||
if (oldLat != null && oldLon != null) {
|
||||
double distance = haversine(oldLat, oldLon, curLat, curLon);
|
||||
if (distance <= MOVEMENT_THRESHOLD_METER) {
|
||||
log.info("位置未发生明显变化({}米 <= {}米),不更新 Redis,deviceImei={}, lat={}, lon={}",
|
||||
distance, MOVEMENT_THRESHOLD_METER, deviceImei, latitude, longitude);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构造位置信息对象
|
||||
Map<String, Object> locationInfo = new LinkedHashMap<>();
|
||||
double[] doubles = LngLonUtil.gps84_To_Gcj02(Double.parseDouble(latitude), Double.parseDouble(longitude));
|
||||
locationInfo.put("deviceImei", deviceImei);
|
||||
locationInfo.put("latitude", doubles[0]);
|
||||
locationInfo.put("longitude", doubles[1]);
|
||||
locationInfo.put("wgs84_latitude", latitude);
|
||||
locationInfo.put("wgs84_longitude", longitude);
|
||||
String address = GetAddressFromLatUtil.getAdd(String.valueOf(doubles[1]), String.valueOf(doubles[0]));
|
||||
locationInfo.put("address", address);
|
||||
locationInfo.put("timestamp", System.currentTimeMillis());
|
||||
// 3. 位置有明显变化,执行保存
|
||||
doSaveLocation(deviceImei, latitude, longitude);
|
||||
|
||||
|
||||
|
||||
String locationJson = JsonUtils.toJsonString(locationInfo);
|
||||
|
||||
// 存储到Redis
|
||||
RedisUtils.setCacheObject(redisKey, locationJson);
|
||||
|
||||
// 存储到一个列表中,保留历史位置信息
|
||||
// String locationHistoryKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX + deviceImei;
|
||||
// RedisUtils.addCacheList(locationHistoryKey, locationJson);
|
||||
// RedisUtils.expire(locationHistoryKey, Duration.ofDays(90));
|
||||
storeDeviceTrajectoryWithSortedSet(deviceImei, locationJson);
|
||||
log.info("位置信息已异步发送到Redis: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
|
||||
} catch (Exception e) {
|
||||
log.error("异步发送位置信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e);
|
||||
log.error("异步发送位置信息到 Redis 失败,deviceImei={}", deviceImei, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 真正执行保存逻辑(抽取出来便于测试和阅读) */
|
||||
private void doSaveLocation(String deviceImei, String wgs84Lat, String wgs84Lon) {
|
||||
|
||||
// Map<String, Object> locationInfo = new LinkedHashMap<>();
|
||||
// locationInfo.put("deviceImei", deviceImei);
|
||||
// locationInfo.put("latitude", gcj02[0]); // GCJ02 纬度
|
||||
// locationInfo.put("longitude", gcj02[1]); // GCJ02 经度
|
||||
// locationInfo.put("wgs84_latitude", wgs84Lat);
|
||||
// locationInfo.put("wgs84_longitude", wgs84Lon);
|
||||
//
|
||||
//
|
||||
// locationInfo.put("address", StringUtils.defaultIfBlank(address, "未知"));
|
||||
// locationInfo.put("timestamp", System.currentTimeMillis());
|
||||
//
|
||||
// String locationJson = JsonUtils.toJsonString(locationInfo);
|
||||
// 使用 fastjson2 零 GC 序列化
|
||||
// WGS84 → GCJ02(火星坐标)
|
||||
double[] gcj02 = LngLonUtil.gps84_To_Gcj02(
|
||||
Double.parseDouble(wgs84Lat),
|
||||
Double.parseDouble(wgs84Lon)
|
||||
);
|
||||
|
||||
String gcj02Lat = String.format("%.6f", gcj02[0]);
|
||||
String gcj02Lon = String.format("%.6f", gcj02[1]);
|
||||
|
||||
// 逆地理编码(可自行决定是否异步)
|
||||
String address = GetAddressFromLatUtil.getAdd(gcj02Lon, gcj02Lat);
|
||||
|
||||
String locationJson = buildLocationJsonFastJSON2(deviceImei, wgs84Lat, wgs84Lon, gcj02Lon, gcj02Lat,address);
|
||||
|
||||
// 主位置信息(最新一条)
|
||||
String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX;
|
||||
RedisUtils.setCacheObject(redisKey, locationJson);
|
||||
|
||||
// 轨迹存储(SortedSet,按时间戳排序)
|
||||
storeDeviceTrajectoryWithSortedSet(deviceImei, locationJson);
|
||||
// 轨迹上传 查询检测对象与围栏关系 记录设备进出围栏事件
|
||||
uploadTrackPointAsync(deviceImei, gcj02Lat, gcj02Lon, address);
|
||||
|
||||
log.info("位置信息已更新 Redis,deviceImei={}, wgs84=({},{})", deviceImei, wgs84Lat, wgs84Lon);
|
||||
}
|
||||
|
||||
private String buildLocationJsonFastJSON2(
|
||||
String deviceImei,
|
||||
String wgs84Lat, String wgs84Lon,
|
||||
String gcj02Lon, String gcj02Lat,String address) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
|
||||
// 直接用默认 writer,零配置,自动零 GC
|
||||
try (JSONWriter w = JSONWriter.of()) {
|
||||
w.startObject();
|
||||
w.writeString("deviceImei"); w.writeColon(); w.writeString(deviceImei); w.writeComma();
|
||||
w.writeString("latitude"); w.writeColon(); w.writeString(gcj02Lat); w.writeComma();
|
||||
w.writeString("longitude"); w.writeColon(); w.writeString(gcj02Lon); w.writeComma();
|
||||
w.writeString("wgs84_latitude"); w.writeColon(); w.writeString(wgs84Lat); w.writeComma();
|
||||
w.writeString("wgs84_longitude"); w.writeColon(); w.writeString(wgs84Lon); w.writeComma();
|
||||
w.writeString("address"); w.writeColon(); w.writeString(StringUtils.defaultIfBlank(address, "未知")); w.writeComma();
|
||||
w.writeString("timestamp"); w.writeColon(); w.writeInt64(timestamp);
|
||||
w.endObject();
|
||||
return w.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传轨迹点并处理电子围栏出入事件(高德猎鹰服务)
|
||||
* 优化点:
|
||||
* 1. 避免重复查询数据库(围栏信息批量缓存)
|
||||
* 2. 防御性编程:全面空指针防护
|
||||
* 3. Redis 操作原子性 + 合理 TTL
|
||||
* 4. 减少不必要的对象创建和流操作
|
||||
* 5. 精确的事件时间使用 locationTime
|
||||
* 6. 失败不阻塞主流程,记录关键错误
|
||||
*/
|
||||
private void uploadTrackPointAsync(String deviceImei,
|
||||
String gcj02Lat,
|
||||
String gcj02Lon,String address) {
|
||||
|
||||
if (StrUtil.hasBlank(deviceImei, gcj02Lat, gcj02Lon)) {
|
||||
log.warn("上传轨迹点参数非法,deviceImei={}, lat={}, lon={}", deviceImei, gcj02Lat, gcj02Lon);
|
||||
return;
|
||||
}
|
||||
long locationTime = System.currentTimeMillis();
|
||||
String fenceStatusKey = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + ":terminal:status";
|
||||
|
||||
try {
|
||||
// 1. 查询设备信息(建议加缓存,热点设备可大幅降低DB压力)
|
||||
Device device = deviceService.selectDeviceByImei(deviceImei);
|
||||
if (device == null || device.getSid() == null || device.getTid() == null) {
|
||||
log.warn("设备不存在或未完成高德终端创建,imei={}", deviceImei);
|
||||
return;
|
||||
}
|
||||
|
||||
Long trid = ObjectUtil.defaultIfNull(device.getTrid(), 0L); // trid 可能为空
|
||||
|
||||
// 2. 上传轨迹点
|
||||
JSONObject point = new JSONObject();
|
||||
point.set("location", String.format("%s,%s", gcj02Lon, gcj02Lat));
|
||||
point.set("locatetime", locationTime);
|
||||
|
||||
JSONArray pointsArray = new JSONArray();
|
||||
pointsArray.add(point);
|
||||
log.info("上传轨迹点开始,deviceImei={}, point={}", deviceImei, point);
|
||||
JSONObject uploadResult = amapTrackUtil.uploadPoints(device.getSid(), device.getTid(), trid, pointsArray);
|
||||
if (uploadResult == null || uploadResult.getInt("errcode", -1) != 10000) {
|
||||
return;
|
||||
}
|
||||
log.info("上传轨迹点成功,deviceImei={}, result={}", deviceImei, uploadResult);
|
||||
// 3. 查询当前围栏状态
|
||||
JSONObject fenceResult = amapTrackUtil.queryTerminalFenceStatus(device.getSid(), null, device.getTid());
|
||||
if (fenceResult == null || fenceResult.getInt("errcode", -1) != 10000) {
|
||||
log.warn("查询设备围栏状态失败,imei={}, result={}", deviceImei, fenceResult);
|
||||
return;
|
||||
}
|
||||
log.info("查询设备围栏状态成功,imei={}, result={}", deviceImei, fenceResult);
|
||||
JSONArray results = fenceResult.getByPath("data.results", JSONArray.class);
|
||||
if (results == null || results.isEmpty()) {
|
||||
// 没有任何围栏关系,清空旧状态
|
||||
RedisUtils.deleteObject(fenceStatusKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 当前在围栏内的 gfid 集合
|
||||
Set<Long> newInFenceGfids = new HashSet<>();
|
||||
List<JSONObject> currentInList = new ArrayList<>();
|
||||
|
||||
for (Object obj : results) {
|
||||
JSONObject item = (JSONObject) obj;
|
||||
if (item.getInt("in", 0) == 1) {
|
||||
Long gfid = item.getLong("gfid");
|
||||
if (gfid != null) {
|
||||
newInFenceGfids.add(gfid);
|
||||
currentInList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 获取上一次的围栏状态
|
||||
List<JSONObject> oldInList = RedisUtils.getCacheObject(fenceStatusKey);
|
||||
Set<Long> oldInFenceGfids = (oldInList == null || oldInList.isEmpty())
|
||||
? Collections.emptySet()
|
||||
: oldInList.stream()
|
||||
.map(o -> o.getLong("gfid"))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 6. 计算出入事件(使用高效的 Set 操作)
|
||||
Set<Long> enteredGfids = new HashSet<>(newInFenceGfids);
|
||||
enteredGfids.removeAll(oldInFenceGfids);// 进入:这次有,上次没有
|
||||
|
||||
Set<Long> exitedGfids = new HashSet<>(oldInFenceGfids);
|
||||
exitedGfids.removeAll(newInFenceGfids);// 离开:上次有,这次没有
|
||||
|
||||
Date eventTime = new Date(locationTime);
|
||||
Double latitude = Double.valueOf(gcj02Lat);
|
||||
Double longitude = Double.valueOf(gcj02Lon);
|
||||
|
||||
// 批量查询围栏信息(关键优化:避免 N+1 查询)
|
||||
Set<Long> allChangedGfids = new HashSet<>();
|
||||
allChangedGfids.addAll(enteredGfids);
|
||||
allChangedGfids.addAll(exitedGfids);
|
||||
|
||||
Map<Long, DeviceGeoFenceVo> fenceMap = new HashMap<>();
|
||||
if (CollUtil.isNotEmpty(allChangedGfids)) {
|
||||
List<DeviceGeoFenceVo> fenceList = deviceGeoFenceMapper.selectVoList(
|
||||
new LambdaQueryWrapper<DeviceGeoFence>().in(DeviceGeoFence::getGfid, allChangedGfids));
|
||||
fenceMap = fenceList.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(DeviceGeoFenceVo::getGfid, v -> v, (a, b) -> a));
|
||||
}
|
||||
|
||||
// 7. 记录进入事件
|
||||
if (CollUtil.isNotEmpty(enteredGfids)) {
|
||||
List<DeviceFenceAccessRecord> enterRecords = new ArrayList<>();
|
||||
for (Long gfid : enteredGfids) {
|
||||
DeviceFenceAccessRecord record = buildFenceRecord(device, fenceMap.get(gfid), 1L, latitude, longitude, eventTime, address);
|
||||
if (record != null) {
|
||||
enterRecords.add(record);
|
||||
}
|
||||
}
|
||||
deviceFenceAccessRecordMapper.insertBatch(enterRecords);
|
||||
log.info("设备进入围栏,imei={}, gfids={}", deviceImei, enteredGfids);
|
||||
}
|
||||
|
||||
// 8. 记录离开事件
|
||||
if (CollUtil.isNotEmpty(exitedGfids)) {
|
||||
List<DeviceFenceAccessRecord> exitRecords = new ArrayList<>();
|
||||
for (Long gfid : exitedGfids) {
|
||||
DeviceFenceAccessRecord record = buildFenceRecord(device, fenceMap.get(gfid), 2L, latitude, longitude, eventTime, address);
|
||||
if (record != null) {
|
||||
exitRecords.add(record);
|
||||
}
|
||||
}
|
||||
deviceFenceAccessRecordMapper.insertBatch(exitRecords);
|
||||
log.info("设备离开围栏,imei={}, gfids={}", deviceImei, exitedGfids);
|
||||
}
|
||||
|
||||
// 9. 更新 Redis 状态(TTL 5分钟,防止频繁查询)
|
||||
if (CollUtil.isNotEmpty(currentInList)) {
|
||||
RedisUtils.setCacheObject(fenceStatusKey, currentInList, Duration.ofMinutes(5));
|
||||
} else {
|
||||
RedisUtils.deleteObject(fenceStatusKey);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("上传轨迹点并处理围栏事件异常,imei={}, lat={}, lon={}, time={}",
|
||||
deviceImei, gcj02Lat, gcj02Lon, locationTime, e);
|
||||
// 可落库待重试或发告警,此处不抛异常影响定位主流程
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建围栏出入记录(提取公共逻辑,避免重复代码)
|
||||
*/
|
||||
private DeviceFenceAccessRecord buildFenceRecord(Device device,
|
||||
DeviceGeoFenceVo fence,
|
||||
Long eventType,
|
||||
Double latitude,
|
||||
Double longitude,
|
||||
Date eventTime,String address) {
|
||||
if (fence == null || fence.getId() == null) {
|
||||
log.warn("围栏信息不存在,gfid 可能已被删除或未绑定");
|
||||
return null;
|
||||
}
|
||||
DeviceFenceAccessRecord bo = new DeviceFenceAccessRecord();
|
||||
bo.setDeviceId(device.getId().toString());
|
||||
bo.setFenceId(fence.getId());
|
||||
bo.setEventType(eventType); // 1=进入, 2=离开
|
||||
bo.setAccuracy(20L);
|
||||
bo.setLatitude(latitude);
|
||||
bo.setLongitude(longitude);
|
||||
bo.setEventTime(eventTime);
|
||||
bo.setTenantId(device.getTenantId());
|
||||
bo.setCreateBy(device.getCreateBy());
|
||||
bo.setCreateDept(device.getCreateDept());
|
||||
bo.setEventAddress(address);
|
||||
return bo;
|
||||
}
|
||||
|
||||
/** 安全解析 double,解析失败返回 null */
|
||||
private Double parseDoubleSafe(String str) {
|
||||
if (StringUtils.isBlank(str)) return null;
|
||||
try {
|
||||
return Double.parseDouble(str.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Haversine 公式计算两点球面距离(米) */
|
||||
private double haversine(double lat1, double lon1, double lat2, double lon2) {
|
||||
double dLat = Math.toRadians(lat2 - lat1);
|
||||
double dLon = Math.toRadians(lon2 - lon1);
|
||||
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
|
||||
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
/** 地球平均半径(米) */
|
||||
double EARTH_RADIUS = 6371_393.0;
|
||||
return EARTH_RADIUS * c;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储设备30天历史轨迹到Redis (使用Sorted Set)
|
||||
*/
|
||||
@ -358,4 +634,6 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule {
|
||||
dto.setMessage(String.format("%s设备已收到通知!", latestLog.getDeviceName()));
|
||||
dto.setUserIds(List.of(latestLog.getCreateBy()));
|
||||
SseMessageUtils.publishMessage(dto);
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
}, 2, TimeUnit.SECONDS);
|
||||
return;
|
||||
}
|
||||
// 1. cover! —— 成功标记
|
||||
|
||||
@ -3,6 +3,7 @@ package com.fuyuanshen.web.controller.device.fence;
|
||||
import java.util.List;
|
||||
|
||||
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.FenceTerminalBo;
|
||||
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
|
||||
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
||||
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
||||
@ -129,4 +130,25 @@ public class DeviceGeoFenceController extends BaseController {
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加电子围栏终端
|
||||
*
|
||||
* @param bo
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/addTerminal")
|
||||
public R<Void> addFenceTerminal(@RequestBody FenceTerminalBo bo) {
|
||||
return toAjax(deviceGeoFenceService.addFenceTerminal(bo));
|
||||
}
|
||||
/**
|
||||
* 删除电子围栏终端
|
||||
*
|
||||
* @param bo
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/delTerminal")
|
||||
public R<Void> delFenceTerminal(@RequestBody FenceTerminalBo bo) {
|
||||
return toAjax(deviceGeoFenceService.delFenceTerminal(bo));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -132,6 +132,8 @@ tenant:
|
||||
- app_menu
|
||||
- app_user_role
|
||||
- app_role_menu
|
||||
- track_service
|
||||
- device_fence_terminal
|
||||
|
||||
# MyBatisPlus配置
|
||||
# https://baomidou.com/config/
|
||||
|
||||
@ -72,6 +72,12 @@ public class DeviceController extends BaseController {
|
||||
return deviceService.queryAll(criteria, page);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/pageTerminal")
|
||||
public TableDataInfo<Device> deviceFenecSelect(DeviceQueryCriteria criteria) throws IOException {
|
||||
Page<Device> page = new Page<>(criteria.getPageNum(), criteria.getPageSize());
|
||||
return deviceService.queryAllTerminal(criteria, page);
|
||||
}
|
||||
|
||||
|
||||
// @Log("新增设备")
|
||||
@Operation(summary = "新增设备")
|
||||
|
||||
@ -0,0 +1,143 @@
|
||||
package com.fuyuanshen.equipment.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fuyuanshen.equipment.domain.Device;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalDelBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalDeviceBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalQueryBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TrackServiceBo;
|
||||
import com.fuyuanshen.equipment.domain.dto.TerminalDeviceDto;
|
||||
import com.fuyuanshen.equipment.domain.vo.TrackServiceVo;
|
||||
import com.fuyuanshen.equipment.service.ITrackServiceService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
|
||||
import com.fuyuanshen.common.log.annotation.Log;
|
||||
import com.fuyuanshen.common.web.core.BaseController;
|
||||
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
|
||||
import com.fuyuanshen.common.core.domain.R;
|
||||
import com.fuyuanshen.common.core.validate.AddGroup;
|
||||
import com.fuyuanshen.common.core.validate.EditGroup;
|
||||
import com.fuyuanshen.common.log.enums.BusinessType;
|
||||
import com.fuyuanshen.common.excel.utils.ExcelUtil;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
* 轨迹服务
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2025-11-24
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/api/trackService")
|
||||
public class TrackServiceController extends BaseController {
|
||||
|
||||
private final ITrackServiceService trackServiceService;
|
||||
|
||||
/**
|
||||
* 查询轨迹服务列表
|
||||
*/
|
||||
@SaCheckPermission("equipment:trackService:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<TrackServiceVo> list(TrackServiceBo bo, PageQuery pageQuery) {
|
||||
return trackServiceService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 轨迹服务列表
|
||||
*/
|
||||
@SaCheckPermission("equipment:trackService:export")
|
||||
@Log(title = "轨迹服务", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(TrackServiceBo bo, HttpServletResponse response) {
|
||||
List<TrackServiceVo> list = trackServiceService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "轨迹服务", TrackServiceVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取轨迹服务详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public R<TrackServiceVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(trackServiceService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增轨迹服务
|
||||
*/
|
||||
@SaCheckPermission("equipment:trackService:add")
|
||||
@Log(title = "轨迹服务", businessType = BusinessType.INSERT)
|
||||
@PostMapping(value = "/add")
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody TrackServiceBo bo) {
|
||||
return toAjax(trackServiceService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改轨迹服务
|
||||
*/
|
||||
@SaCheckPermission("equipment:trackService:edit")
|
||||
@Log(title = "轨迹服务", businessType = BusinessType.UPDATE)
|
||||
@PostMapping(value = "/update")
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody TrackServiceBo bo) {
|
||||
return toAjax(trackServiceService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除轨迹服务
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@SaCheckPermission("equipment:trackService:remove")
|
||||
@Log(title = "轨迹服务", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping(value = "/delete")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(trackServiceService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
@Log(title = "高德添加终端设备")
|
||||
@PostMapping(value = "/terminal")
|
||||
public R<Void> terminalDevice(@RequestBody TerminalDeviceBo model) {
|
||||
trackServiceService.terminal(model);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Log(title = "高德移除终端设备")
|
||||
@DeleteMapping(value = "/delTerminal/{ids}")
|
||||
public R<Void> delTerminalDevice(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(trackServiceService.delTerminalDevice(List.of(ids), true));
|
||||
}
|
||||
|
||||
@Log(title = "高德移除终端设备")
|
||||
@DeleteMapping(value = "/del/Terminal")
|
||||
public R<Void> delTerminalDevice(TerminalDelBo bo) {
|
||||
return toAjax(trackServiceService.delTerminalDevice(bo, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 高德终端设备列表
|
||||
*/
|
||||
@GetMapping("/listTerminal")
|
||||
public TableDataInfo<TerminalDeviceDto> listTerminal(TerminalQueryBo bo) {
|
||||
return trackServiceService.listTerminal(bo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 高德终端设备列表(关键字查询)
|
||||
*/
|
||||
@GetMapping("/searchTerminal")
|
||||
public TableDataInfo<TerminalDeviceDto> searchTerminal(TerminalQueryBo bo) {
|
||||
return trackServiceService.searchTerminal(bo);
|
||||
}
|
||||
}
|
||||
@ -167,4 +167,20 @@ public class Device extends TenantEntity {
|
||||
*/
|
||||
private Integer onlineStatus;
|
||||
|
||||
/**
|
||||
* 高德服务ID
|
||||
*/
|
||||
@Schema(title = "服务ID(高德)")
|
||||
private Long sid;
|
||||
/**
|
||||
* 高德终端ID
|
||||
*/
|
||||
@Schema(title = "终端ID(高德)")
|
||||
private Long tid;
|
||||
/**
|
||||
* 高德轨迹ID
|
||||
*/
|
||||
@Schema(title = "轨迹ID(高德)")
|
||||
private Long trid;
|
||||
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.fuyuanshen.equipment.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
|
||||
import com.fuyuanshen.common.tenant.core.TenantEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Date;
|
||||
@ -18,7 +19,7 @@ import java.io.Serial;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("device_fence_access_record")
|
||||
public class DeviceFenceAccessRecord extends BaseEntity {
|
||||
public class DeviceFenceAccessRecord extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
package com.fuyuanshen.equipment.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 围栏终端关联
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@TableName("device_fence_terminal")
|
||||
public class DeviceFenceTerminal {
|
||||
/**
|
||||
* 围栏ID
|
||||
*/
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long fenceId;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private Long deviceId;
|
||||
}
|
||||
@ -58,6 +58,14 @@ public class DeviceGeoFence extends BaseEntity {
|
||||
* 是否激活
|
||||
*/
|
||||
private Long isActive;
|
||||
/**
|
||||
* 高德服务ID
|
||||
*/
|
||||
private Long sid;
|
||||
/**
|
||||
* 高德围栏ID
|
||||
*/
|
||||
private Long gfid;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
package com.fuyuanshen.equipment.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 轨迹服务对象 track_service
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2025-11-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("track_service")
|
||||
public class TrackService extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 猎鹰服务ID
|
||||
*/
|
||||
private Long sid;
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*/
|
||||
private String sname;
|
||||
|
||||
|
||||
}
|
||||
@ -70,6 +70,14 @@ public class DeviceGeoFenceBo extends BaseEntity {
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
/**
|
||||
* 高德服务ID
|
||||
*/
|
||||
private Long sid;
|
||||
/**
|
||||
* 高德围栏ID
|
||||
*/
|
||||
private Long gfid;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
package com.fuyuanshen.equipment.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class FenceTerminalBo {
|
||||
/**
|
||||
* 围栏ID
|
||||
*/
|
||||
private Long fenceId;
|
||||
/**
|
||||
* 设备IDs
|
||||
*/
|
||||
private List<Long> deviceIds;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.fuyuanshen.equipment.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TerminalDelBo {
|
||||
/**
|
||||
* 猎鹰服务ID
|
||||
*/
|
||||
private Long sid;
|
||||
/**
|
||||
* 轨迹ID
|
||||
*/
|
||||
private Long trid;
|
||||
/**
|
||||
* 终端ID
|
||||
*/
|
||||
private Long tid;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.fuyuanshen.equipment.domain.bo;
|
||||
|
||||
import com.fuyuanshen.equipment.domain.form.DeviceForm;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class TerminalDeviceBo {
|
||||
/**
|
||||
* 猎鹰服务ID
|
||||
*/
|
||||
private Long sid;
|
||||
/**
|
||||
* 设备列表
|
||||
*/
|
||||
List<DeviceForm> deviceList;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.fuyuanshen.equipment.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Value;
|
||||
|
||||
@Data
|
||||
public class TerminalQueryBo {
|
||||
/**
|
||||
* 猎鹰服务ID
|
||||
*/
|
||||
private Long sid;
|
||||
/**
|
||||
* 名称查询
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 终端ID
|
||||
*/
|
||||
private Long tid;
|
||||
/**
|
||||
* 关键字搜索
|
||||
*/
|
||||
private String keywords;
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
private int page = 1;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
private int pagesize = 10;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.fuyuanshen.equipment.domain.bo;
|
||||
|
||||
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
|
||||
import com.fuyuanshen.equipment.domain.TrackService;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 轨迹服务业务对象 track_service
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2025-11-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AutoMapper(target = TrackService.class, reverseConvertGenerate = false)
|
||||
public class TrackServiceBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 猎鹰服务ID
|
||||
*/
|
||||
private Long sid;
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*/
|
||||
private String sname;
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.fuyuanshen.equipment.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor // 必须
|
||||
public class TerminalDeviceDto {
|
||||
/**
|
||||
* 终端设备名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 终端设备ID
|
||||
*/
|
||||
private Long tid;
|
||||
/**
|
||||
* 终端设备描述
|
||||
*/
|
||||
private String desc;
|
||||
/**
|
||||
* 终端设备创建时间
|
||||
*/
|
||||
private String createtime;
|
||||
/**
|
||||
* 终端设备最后定位时间
|
||||
*/
|
||||
private String locatetime;
|
||||
}
|
||||
@ -122,6 +122,18 @@ public class DeviceQueryCriteria extends BaseEntity {
|
||||
* online_status
|
||||
*/
|
||||
private Integer onlineStatus;
|
||||
/**
|
||||
* 高德终端ID是否存在
|
||||
*/
|
||||
private Boolean isTid;
|
||||
/**
|
||||
* 高德服务ID
|
||||
*/
|
||||
private String sid;
|
||||
/**
|
||||
* 围栏ID
|
||||
*/
|
||||
private Long fenceId;
|
||||
|
||||
/**
|
||||
* 绑定状态
|
||||
|
||||
@ -101,6 +101,14 @@ public class DeviceGeoFenceVo implements Serializable {
|
||||
*/
|
||||
// @ExcelProperty(value = "更新时间")
|
||||
private Date updateTime;
|
||||
/**
|
||||
* 高德服务ID
|
||||
*/
|
||||
private Long sid;
|
||||
/**
|
||||
* 高德围栏ID
|
||||
*/
|
||||
private Long gfid;
|
||||
|
||||
|
||||
public void setAreaTypeNameByAreaType() {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.fuyuanshen.equipment.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fuyuanshen.common.tenant.core.TenantEntity;
|
||||
import com.fuyuanshen.equipment.domain.DeviceRepairRecords;
|
||||
@ -34,31 +36,39 @@ public class DeviceRepairRecordsVo extends TenantEntity implements Serializable
|
||||
/**
|
||||
* 维修记录ID
|
||||
*/
|
||||
@ExcelProperty(value = "维修记录ID")
|
||||
// @ExcelProperty(value = "维修记录ID")
|
||||
private Long recordId;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
@ExcelProperty(value = "设备ID")
|
||||
// @ExcelProperty(value = "设备ID")
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
@ExcelProperty(value = "设备名称")
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 维修时间
|
||||
*/
|
||||
@ExcelProperty(value = "维修时间")
|
||||
private Date repairTime;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ColumnWidth(20)
|
||||
private String repairTime;
|
||||
|
||||
/**
|
||||
* 维修部位
|
||||
* 损坏部位
|
||||
*/
|
||||
@ExcelProperty(value = "维修部位")
|
||||
@ExcelProperty(value = "损坏部位")
|
||||
private String repairPart;
|
||||
|
||||
/**
|
||||
* 维修原因
|
||||
* 损坏原因
|
||||
*/
|
||||
@ExcelProperty(value = "维修原因")
|
||||
@ExcelProperty(value = "损坏原因")
|
||||
private String repairReason;
|
||||
|
||||
/**
|
||||
@ -66,11 +76,7 @@ public class DeviceRepairRecordsVo extends TenantEntity implements Serializable
|
||||
*/
|
||||
@ExcelProperty(value = "维修人员")
|
||||
private String repairPerson;
|
||||
/**
|
||||
* 维修人员
|
||||
*/
|
||||
@ExcelProperty(value = "设备名称")
|
||||
private String deviceName;
|
||||
|
||||
|
||||
private List<DeviceRepairImagesVo> images;
|
||||
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
package com.fuyuanshen.equipment.domain.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import com.fuyuanshen.common.excel.annotation.ExcelDictFormat;
|
||||
import com.fuyuanshen.common.excel.convert.ExcelDictConvert;
|
||||
import com.fuyuanshen.equipment.domain.TrackService;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 轨迹服务视图对象 track_service
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2025-11-24
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = TrackService.class)
|
||||
public class TrackServiceVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExcelProperty(value = "")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 猎鹰服务ID
|
||||
*/
|
||||
@ExcelProperty(value = "猎鹰服务ID")
|
||||
private Long sid;
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*/
|
||||
@ExcelProperty(value = "服务名称")
|
||||
private String sname;
|
||||
|
||||
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package com.fuyuanshen.equipment.domain.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -103,5 +104,20 @@ public class WebDeviceVo implements Serializable {
|
||||
* 分享用户数量
|
||||
*/
|
||||
private Integer shareUsersNumber;
|
||||
/**
|
||||
* 高德服务ID
|
||||
*/
|
||||
@Schema(title = "服务ID(高德)")
|
||||
private Long sid;
|
||||
/**
|
||||
* 高德终端ID
|
||||
*/
|
||||
@Schema(title = "终端ID(高德)")
|
||||
private Long tid;
|
||||
/**
|
||||
* 高德轨迹ID
|
||||
*/
|
||||
@Schema(title = "轨迹ID(高德)")
|
||||
private Long trid;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package com.fuyuanshen.equipment.mapper;
|
||||
|
||||
import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import com.fuyuanshen.equipment.domain.DeviceFenceTerminal;
|
||||
|
||||
public interface DeviceFenceTerminalMapper extends BaseMapperPlus<DeviceFenceTerminal, DeviceFenceTerminal> {
|
||||
}
|
||||
@ -32,6 +32,8 @@ public interface DeviceMapper extends BaseMapper<Device> {
|
||||
|
||||
List<Device> findAll(@Param("criteria") DeviceQueryCriteria criteria);
|
||||
|
||||
IPage<Device> findAllTerminal(@Param("criteria") DeviceQueryCriteria criteria, Page<Device> page);
|
||||
|
||||
List<Device> findAllDevices(@Param("criteria") DeviceQueryCriteria criteria);
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
package com.fuyuanshen.equipment.mapper;
|
||||
|
||||
import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import com.fuyuanshen.equipment.domain.TrackService;
|
||||
import com.fuyuanshen.equipment.domain.vo.TrackServiceVo;
|
||||
|
||||
/**
|
||||
* 轨迹服务Mapper接口
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2025-11-24
|
||||
*/
|
||||
public interface TrackServiceMapper extends BaseMapperPlus<TrackService, TrackServiceVo> {
|
||||
|
||||
}
|
||||
@ -30,6 +30,8 @@ public interface DeviceService extends IService<Device> {
|
||||
*/
|
||||
TableDataInfo<Device> queryAll(DeviceQueryCriteria criteria, Page<Device> page) throws IOException;
|
||||
|
||||
TableDataInfo<Device> queryAllTerminal(DeviceQueryCriteria criteria, Page<Device> page) throws IOException;
|
||||
|
||||
/**
|
||||
* 查询所有数据不分页
|
||||
*
|
||||
|
||||
@ -6,6 +6,7 @@ import com.fuyuanshen.common.mybatis.core.page.PageQuery;
|
||||
import com.fuyuanshen.equipment.domain.Device;
|
||||
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
|
||||
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.FenceTerminalBo;
|
||||
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
|
||||
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
||||
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
||||
@ -78,4 +79,8 @@ public interface IDeviceGeoFenceService extends IService<DeviceGeoFence> {
|
||||
* @return 位置检查结果
|
||||
*/
|
||||
FenceCheckResponse checkPosition(FenceCheckRequest request);
|
||||
|
||||
Boolean addFenceTerminal(FenceTerminalBo bo);
|
||||
|
||||
Boolean delFenceTerminal(FenceTerminalBo bo);
|
||||
}
|
||||
|
||||
@ -0,0 +1,103 @@
|
||||
package com.fuyuanshen.equipment.service;
|
||||
|
||||
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalDelBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalDeviceBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalQueryBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TrackServiceBo;
|
||||
import com.fuyuanshen.equipment.domain.dto.TerminalDeviceDto;
|
||||
import com.fuyuanshen.equipment.domain.vo.TrackServiceVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 轨迹服务Service接口
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2025-11-24
|
||||
*/
|
||||
public interface ITrackServiceService {
|
||||
|
||||
/**
|
||||
* 查询轨迹服务
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 轨迹服务
|
||||
*/
|
||||
TrackServiceVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询轨迹服务列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 轨迹服务分页列表
|
||||
*/
|
||||
TableDataInfo<TrackServiceVo> queryPageList(TrackServiceBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询符合条件的轨迹服务列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 轨迹服务列表
|
||||
*/
|
||||
List<TrackServiceVo> queryList(TrackServiceBo bo);
|
||||
|
||||
/**
|
||||
* 新增轨迹服务
|
||||
*
|
||||
* @param bo 轨迹服务
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertByBo(TrackServiceBo bo);
|
||||
|
||||
/**
|
||||
* 修改轨迹服务
|
||||
*
|
||||
* @param bo 轨迹服务
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByBo(TrackServiceBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除轨迹服务信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 高德添加终端设备
|
||||
*
|
||||
* @param terminalDeviceBo 添加集合
|
||||
*/
|
||||
void terminal(TerminalDeviceBo terminalDeviceBo);
|
||||
/**
|
||||
* 高德移除终端设备
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean delTerminalDevice(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
Boolean delTerminalDevice(TerminalDelBo bo, Boolean isValid);
|
||||
/**
|
||||
* 查询设备列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 设备列表
|
||||
*/
|
||||
TableDataInfo<TerminalDeviceDto> listTerminal(TerminalQueryBo bo);
|
||||
/**
|
||||
* 关键字查询设备
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 设备列表
|
||||
*/
|
||||
TableDataInfo<TerminalDeviceDto> searchTerminal(TerminalQueryBo bo);
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.fuyuanshen.equipment.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@ -10,14 +11,17 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fuyuanshen.equipment.domain.Device;
|
||||
import com.fuyuanshen.equipment.domain.DeviceFenceTerminal;
|
||||
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
|
||||
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.FenceTerminalBo;
|
||||
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
|
||||
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
||||
import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo;
|
||||
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
||||
import com.fuyuanshen.equipment.mapper.DeviceFenceTerminalMapper;
|
||||
import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper;
|
||||
import com.fuyuanshen.equipment.mapper.DeviceMapper;
|
||||
import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService;
|
||||
@ -25,6 +29,7 @@ import com.fuyuanshen.equipment.service.IDeviceFenceStatusService;
|
||||
import com.fuyuanshen.equipment.service.IDeviceGeoFenceService;
|
||||
import com.fuyuanshen.equipment.utils.map.GeoFenceChecker;
|
||||
import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil;
|
||||
import com.fuyuanshen.system.domain.SysRoleMenu;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -48,6 +53,7 @@ public class DeviceGeoFenceServiceImpl extends ServiceImpl<DeviceGeoFenceMapper
|
||||
private final IDeviceFenceStatusService fenceStatusService; // 添加此行
|
||||
|
||||
private final IDeviceFenceAccessRecordService fenceAccessRecordService; // 添加此行
|
||||
private final DeviceFenceTerminalMapper deviceFenceTerminalMapper;
|
||||
|
||||
|
||||
|
||||
@ -269,5 +275,32 @@ public class DeviceGeoFenceServiceImpl extends ServiceImpl<DeviceGeoFenceMapper
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean addFenceTerminal(FenceTerminalBo bo) {
|
||||
// 新增围栏与终端关联信息
|
||||
List<DeviceFenceTerminal> list = new ArrayList<>();
|
||||
for (Long deviceId : bo.getDeviceIds()) {
|
||||
DeviceFenceTerminal rm = new DeviceFenceTerminal();
|
||||
rm.setFenceId(bo.getFenceId());
|
||||
rm.setDeviceId(deviceId);
|
||||
list.add(rm);
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
return deviceFenceTerminalMapper.insertBatch(list);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean delFenceTerminal(FenceTerminalBo bo) {
|
||||
if (bo.getFenceId() == null || bo.getDeviceIds() == null || bo.getDeviceIds().isEmpty()) {
|
||||
throw new IllegalArgumentException("围栏ID或设备ID不能为空");
|
||||
}
|
||||
LambdaUpdateWrapper<DeviceFenceTerminal> lambda = new LambdaUpdateWrapper<DeviceFenceTerminal>()
|
||||
.eq(DeviceFenceTerminal::getFenceId, bo.getFenceId())
|
||||
.in(DeviceFenceTerminal::getDeviceId, bo.getDeviceIds());
|
||||
return deviceFenceTerminalMapper.delete(lambda) > 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -126,6 +126,26 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
||||
return new TableDataInfo<Device>(records, devices.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<Device> queryAllTerminal(DeviceQueryCriteria criteria, Page<Device> page) throws IOException {
|
||||
// 角色管理员
|
||||
Long userId = LoginHelper.getUserId();
|
||||
List<SysRoleVo> roles = roleService.selectRolesAuthByUserId(userId);
|
||||
boolean isAdmin = false;
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
for (SysRoleVo role : roles) {
|
||||
if (role.getRoleKey().equals("admin")) {
|
||||
isAdmin = true;
|
||||
criteria.setIsAdmin(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IPage<Device> devices = deviceMapper.findAllTerminal(criteria, page);
|
||||
return new TableDataInfo<Device>(devices.getRecords(), devices.getTotal());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Device> queryAll(DeviceQueryCriteria criteria) {
|
||||
|
||||
@ -0,0 +1,404 @@
|
||||
package com.fuyuanshen.equipment.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.fuyuanshen.common.core.utils.MapstructUtils;
|
||||
import com.fuyuanshen.common.core.utils.StringUtils;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fuyuanshen.equipment.domain.Device;
|
||||
import com.fuyuanshen.equipment.domain.TrackService;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalDelBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalDeviceBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TerminalQueryBo;
|
||||
import com.fuyuanshen.equipment.domain.bo.TrackServiceBo;
|
||||
import com.fuyuanshen.equipment.domain.dto.TerminalDeviceDto;
|
||||
import com.fuyuanshen.equipment.domain.form.DeviceForm;
|
||||
import com.fuyuanshen.equipment.domain.vo.TrackServiceVo;
|
||||
import com.fuyuanshen.equipment.mapper.DeviceMapper;
|
||||
import com.fuyuanshen.equipment.mapper.TrackServiceMapper;
|
||||
import com.fuyuanshen.equipment.service.ITrackServiceService;
|
||||
import com.fuyuanshen.equipment.utils.map.AmapTrackUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 轨迹服务Service业务层处理
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2025-11-24
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class TrackServiceServiceImpl implements ITrackServiceService {
|
||||
|
||||
private final TrackServiceMapper baseMapper;
|
||||
private final DeviceMapper deviceMapper;
|
||||
private final AmapTrackUtil amapTrackUtil;
|
||||
|
||||
/**
|
||||
* 查询轨迹服务
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 轨迹服务
|
||||
*/
|
||||
@Override
|
||||
public TrackServiceVo queryById(Long id){
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询轨迹服务列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 轨迹服务分页列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<TrackServiceVo> queryPageList(TrackServiceBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<TrackService> lqw = buildQueryWrapper(bo);
|
||||
Page<TrackServiceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询符合条件的轨迹服务列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 轨迹服务列表
|
||||
*/
|
||||
@Override
|
||||
public List<TrackServiceVo> queryList(TrackServiceBo bo) {
|
||||
LambdaQueryWrapper<TrackService> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<TrackService> buildQueryWrapper(TrackServiceBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<TrackService> lqw = Wrappers.lambdaQuery();
|
||||
lqw.orderByAsc(TrackService::getId);
|
||||
lqw.eq(bo.getSid() != null, TrackService::getSid, bo.getSid());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getSname()), TrackService::getSname, bo.getSname());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增轨迹服务
|
||||
*
|
||||
* @param bo 轨迹服务
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(TrackServiceBo bo) {
|
||||
TrackService add = MapstructUtils.convert(bo, TrackService.class);
|
||||
validEntityBeforeSave(add);
|
||||
/* 1. 先查高德侧是否已存在同名服务 */
|
||||
JSONObject listResult = amapTrackUtil.listService();
|
||||
if (listResult == null || listResult.getInt("errcode") != 10000) {
|
||||
throw new RuntimeException("查询高德轨迹服务列表失败");
|
||||
}
|
||||
log.warn("查询: {}", listResult);
|
||||
String serviceName = add.getSname();
|
||||
String sid = null;
|
||||
|
||||
// 取 data 数组,遍历匹配 name
|
||||
JSONArray serviceArray = listResult.getByPath("data.results", JSONArray.class);
|
||||
if (serviceArray != null) {
|
||||
for (int i = 0; i < serviceArray.size(); i++) {
|
||||
JSONObject item = serviceArray.getJSONObject(i);
|
||||
if (serviceName.equals(item.getStr("name"))) {
|
||||
sid = item.getStr("sid"); // 已存在,直接复用
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. 不存在才创建 */
|
||||
if (sid == null) {
|
||||
JSONObject createResult = amapTrackUtil.createService(serviceName);
|
||||
if (createResult == null || createResult.getInt("errcode") != 10000) {
|
||||
throw new RuntimeException("生成高德轨迹服务失败");
|
||||
}
|
||||
sid = createResult.getByPath("data.sid", String.class);
|
||||
}
|
||||
|
||||
add.setSid(Long.valueOf(sid));
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改轨迹服务
|
||||
*
|
||||
* @param bo 轨迹服务
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(TrackServiceBo bo) {
|
||||
TrackService update = MapstructUtils.convert(bo, TrackService.class);
|
||||
validEntityBeforeSave(update);
|
||||
if(update.getId() == null) {
|
||||
throw new RuntimeException("轨迹服务ID不能为空");
|
||||
}
|
||||
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(TrackService entity){
|
||||
if(entity == null) {
|
||||
throw new RuntimeException("请输入信息");
|
||||
}
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
if(!StringUtils.isNotEmpty(entity.getSname())) {
|
||||
throw new RuntimeException("轨迹服务名称不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并批量删除轨迹服务信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if(isValid){
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteByIds(ids) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean delTerminalDevice(Collection<Long> ids, Boolean isValid) {
|
||||
if (CollectionUtils.isEmpty(ids)) {
|
||||
throw new IllegalArgumentException("请选择要移除的设备");
|
||||
}
|
||||
|
||||
// 1. 批量查询(一次 SQL,性能提升 10 倍+)
|
||||
List<Device> deviceList = deviceMapper.selectList(
|
||||
new LambdaQueryWrapper<Device>()
|
||||
.in(Device::getId, ids)
|
||||
.select(Device::getId, Device::getSid, Device::getTid, Device::getTrid)
|
||||
);
|
||||
|
||||
if (CollectionUtils.isEmpty(deviceList)) {
|
||||
log.info("未找到要移除的设备,ids={}", ids);
|
||||
return true; // 幂等:没找到也算成功
|
||||
}
|
||||
|
||||
// 2. 业务校验(可插拔)
|
||||
if (isValid) {
|
||||
validateBeforeDelete(deviceList); // 你自己实现具体校验逻辑
|
||||
}
|
||||
|
||||
// 3. 批量清理高德资源 + 本地数据(精准对应,顺序正确)
|
||||
for (Device device : deviceList) {
|
||||
Long sid = device.getSid();
|
||||
Long tid = device.getTid();
|
||||
Long trid = device.getTrid();
|
||||
|
||||
if (sid == null) {
|
||||
log.warn("设备无高德服务ID,跳过高德清理,deviceId={}", device.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 先删轨迹(必须 tid + trid),再删终端(只需 tid)
|
||||
if (trid != null && tid != null) {
|
||||
safeDeleteTrack(sid, tid, trid);
|
||||
}
|
||||
if (tid != null) {
|
||||
safeDeleteTerminal(sid, tid);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量更新数据库(性能极高,避免 N+1 update)
|
||||
int updated = deviceMapper.update(null,
|
||||
new LambdaUpdateWrapper<Device>()
|
||||
.in(Device::getId, ids)
|
||||
.set(Device::getTid, null)
|
||||
.set(Device::getTrid, null)
|
||||
.set(Device::getSid, null)
|
||||
);
|
||||
|
||||
log.info("成功解绑高德终端,设备数量={},实际更新记录数={}", deviceList.size(), updated);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean delTerminalDevice(TerminalDelBo bo, Boolean isValid) {
|
||||
var list = deviceMapper.selectList(new LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSid, bo.getSid())
|
||||
.eq(Device::getTid, bo.getTid()));
|
||||
if (list.isEmpty()) {
|
||||
throw new IllegalArgumentException("本地设备已绑定不允许删除");
|
||||
}
|
||||
// 先删轨迹(必须 tid + trid),再删终端(只需 tid)
|
||||
if (bo.getSid() != null && bo.getTid() != null) {
|
||||
safeDeleteTrack(bo.getSid(), bo.getTid(), bo.getTrid());
|
||||
}
|
||||
if (bo.getTid() != null) {
|
||||
safeDeleteTerminal(bo.getSid(),bo.getTid());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询终端列表
|
||||
*
|
||||
* @param bo 查询参数
|
||||
* @return 终端列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<TerminalDeviceDto> listTerminal(TerminalQueryBo bo) {
|
||||
JSONObject terminalRes = amapTrackUtil.listTerminal(bo.getSid(), bo.getTid(), bo.getName(), bo.getPage());
|
||||
List<TerminalDeviceDto> list = terminalRes.getByPath("data.results", JSONArray.class)
|
||||
.toList(TerminalDeviceDto.class);
|
||||
Integer count = terminalRes.getByPath("data.count", Integer.class);
|
||||
return new TableDataInfo<TerminalDeviceDto>(list, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索终端列表
|
||||
*
|
||||
* @param bo 搜索参数
|
||||
* @return 终端列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<TerminalDeviceDto> searchTerminal(TerminalQueryBo bo) {
|
||||
JSONObject terminalRes = amapTrackUtil.searchTerminal(bo.getSid(), bo.getKeywords(), bo.getPage(), bo.getPagesize());
|
||||
log.info("终端列表:{}", terminalRes);
|
||||
List<TerminalDeviceDto> list = terminalRes.getByPath("data.results", JSONArray.class)
|
||||
.toList(TerminalDeviceDto.class);
|
||||
Integer count = terminalRes.getByPath("data.count", Integer.class);
|
||||
return new TableDataInfo<TerminalDeviceDto>(list, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务校验钩子
|
||||
*/
|
||||
private void validateBeforeDelete(List<Device> deviceList) {
|
||||
// 示例:校验设备是否在线、是否有未完成订单等
|
||||
// throw new BusinessException("xxx设备正在使用中,不能解绑");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void terminal(TerminalDeviceBo bo) {
|
||||
if (bo.getSid() == null) {
|
||||
throw new IllegalArgumentException("高德服务ID不能为空");
|
||||
}
|
||||
if (CollectionUtils.isEmpty(bo.getDeviceList())) {
|
||||
throw new IllegalArgumentException("请选择设备");
|
||||
}
|
||||
|
||||
Long sid = bo.getSid();
|
||||
|
||||
// 使用对象封装,便于补偿时知道 tid 和 trid 的对应关系(关键!)
|
||||
class CreatedResource {
|
||||
Long tid;
|
||||
Long trid;
|
||||
}
|
||||
List<CreatedResource> createdResources = new ArrayList<>();
|
||||
|
||||
try {
|
||||
for (DeviceForm form : bo.getDeviceList()) {
|
||||
String terminalName = String.format("%s_%s", form.getTypeName(), form.getDeviceImei());
|
||||
|
||||
// Step 1: 创建终端
|
||||
JSONObject terminalRes = amapTrackUtil.createTerminal(sid, terminalName);
|
||||
if (!isSuccess(terminalRes)) {
|
||||
throw new RuntimeException("创建设备终端失败:" + terminalRes);
|
||||
}
|
||||
Long tid = terminalRes.getByPath("data.tid", Long.class);
|
||||
|
||||
// Step 2: 创建轨迹
|
||||
JSONObject trackRes = amapTrackUtil.createTrace(sid, tid);
|
||||
if (!isSuccess(trackRes)) {
|
||||
throw new RuntimeException("创建轨迹失败:" + trackRes);
|
||||
}
|
||||
Long trid = trackRes.getByPath("data.trid", Long.class);
|
||||
|
||||
// 3. 更新本地设备
|
||||
Device device = new Device();
|
||||
device.setId(form.getId());
|
||||
device.setTid(tid);
|
||||
device.setTrid(trid);
|
||||
device.setSid(sid);
|
||||
deviceMapper.updateById(device);
|
||||
|
||||
// 记录本次创建的资源(用于精确补偿)
|
||||
CreatedResource resource = new CreatedResource();
|
||||
resource.tid = tid;
|
||||
resource.trid = trid;
|
||||
createdResources.add(resource);
|
||||
|
||||
// 可落盘日志(用于极端情况人工排查)
|
||||
log.warn("高德添加设备终端 sid={} tid={} trid={} deviceId={}",
|
||||
sid, tid, trid, form.getId());
|
||||
}
|
||||
|
||||
// 全部成功 → 清空内存集合
|
||||
createdResources.clear();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("绑定高德终端失败,正在执行补偿清理,已创建资源数={}", createdResources.size(), e);
|
||||
|
||||
// 精确补偿:先删轨迹(需要 sid + tid + trid),再删终端(需要 sid + tid)
|
||||
for (CreatedResource r : createdResources) {
|
||||
if (r.trid != null) {
|
||||
safeDeleteTrack(sid, r.tid, r.trid); // 现在传 3 个参数
|
||||
}
|
||||
if (r.tid != null) {
|
||||
safeDeleteTerminal(sid, r.tid);
|
||||
}
|
||||
}
|
||||
|
||||
throw e; // 抛异常让调用方知道失败
|
||||
}
|
||||
}
|
||||
|
||||
/** 安全删除轨迹 */
|
||||
private void safeDeleteTrack(Long sid, Long tid, Long trid) {
|
||||
try {
|
||||
// 假设你的工具方法是这样定义的:
|
||||
// amapTrackUtil.deleteTrack(sid, tid, trid);
|
||||
amapTrackUtil.deleteTrace(sid, tid, trid);
|
||||
log.info("补偿删除轨迹成功 sid={} tid={} trid={}", sid, tid, trid);
|
||||
} catch (Exception ex) {
|
||||
log.error("补偿删除轨迹失败 sid={} tid={} trid={},请人工介入或等定时任务处理", sid, tid, trid, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/** 安全删除终端 */
|
||||
private void safeDeleteTerminal(Long sid, Long tid) {
|
||||
try {
|
||||
amapTrackUtil.deleteTerminal(sid, tid);
|
||||
log.info("补偿删除终端成功 sid={} tid={}", sid, tid);
|
||||
} catch (Exception ex) {
|
||||
log.error("补偿删除终端失败 sid={} tid={},请人工介入或等定时任务处理", sid, tid, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuccess(JSONObject json) {
|
||||
return json != null && Objects.equals(json.getInt("errcode"), 10000);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,276 @@
|
||||
package com.fuyuanshen.equipment.utils.map;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 高德猎鹰轨迹服务 工具类
|
||||
* <p>
|
||||
* 优化点:
|
||||
* 1. 引入 Slf4j 增加日志记录
|
||||
* 2. 使用 UriComponentsBuilder 处理 URL 拼接和参数编码
|
||||
* 3. 统一异常处理和请求封装
|
||||
* 4. 提取常量
|
||||
* </p>
|
||||
* 官网文档:https://lbs.amap.com/api/track
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AmapTrackUtil {
|
||||
|
||||
private static final String BASE_URL = "https://tsapi.amap.com/v1/track";
|
||||
|
||||
// @Value("${gaode.key:}")
|
||||
private final String key = "84a12a692ae378effdf741e16d584cd3";
|
||||
|
||||
|
||||
/* ========================== 1. 轨迹服务 ========================== */
|
||||
|
||||
/** 创建服务 */
|
||||
public JSONObject createService(String name) {
|
||||
return sendRequest(HttpMethod.POST, "/service/add", builder -> builder.queryParam("name", name), null);
|
||||
}
|
||||
|
||||
/** 更新服务 */
|
||||
public JSONObject updateService(String sid, String name) {
|
||||
return sendRequest(HttpMethod.POST, "/service/update",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("name", name), null);
|
||||
}
|
||||
|
||||
/** 查询服务列表 */
|
||||
public JSONObject listService() {
|
||||
return sendRequest(HttpMethod.GET, "/service/list", null, null);
|
||||
}
|
||||
|
||||
/** 删除服务 */
|
||||
public JSONObject deleteService(String sid) {
|
||||
return sendRequest(HttpMethod.POST, "/service/delete", builder -> builder.queryParam("sid", sid), null);
|
||||
}
|
||||
|
||||
/* ========================== 2. 终端 ========================== */
|
||||
|
||||
/** 创建终端 */
|
||||
public JSONObject createTerminal(Long sid, String name) {
|
||||
return sendRequest(HttpMethod.POST, "/terminal/add",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("name", name), null);
|
||||
}
|
||||
|
||||
/** 查询终端列表 */
|
||||
public JSONObject listTerminal(Long sid, Long tid, String name, int page) {
|
||||
return sendRequest(HttpMethod.GET, "/terminal/list",
|
||||
builder -> {
|
||||
builder.queryParam("sid", sid).queryParam("page", page);
|
||||
if (StrUtil.isNotBlank(name)) {
|
||||
builder.queryParam("name", name);
|
||||
}
|
||||
if (tid != null) {
|
||||
builder.queryParam("tid", tid);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
/** 查询终端列表 */
|
||||
public JSONObject searchTerminal(Long sid, String keywords, int page, int pageSize) {
|
||||
return sendRequest(HttpMethod.GET, "/terminal/list",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("keywords", keywords).queryParam("page", page).queryParam("pageSize", pageSize), null);
|
||||
}
|
||||
|
||||
/** 删除终端 */
|
||||
public JSONObject deleteTerminal(Long sid, Long tid) {
|
||||
return sendRequest(HttpMethod.POST, "/terminal/delete",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("tid", tid), null);
|
||||
}
|
||||
|
||||
/* ========================== 3. 轨迹 ========================== */
|
||||
|
||||
/** 创建轨迹 */
|
||||
public JSONObject createTrace(Long sid, Long tid) {
|
||||
return sendRequest(HttpMethod.POST, "/trace/add",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("tid", tid), null);
|
||||
}
|
||||
|
||||
/** 查询轨迹(按 trid) */
|
||||
public JSONObject queryTrace(String sid, String tid, String trid) {
|
||||
return sendRequest(HttpMethod.GET, "/terminal/trsearch",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("tid", tid).queryParam("trid", trid), null);
|
||||
}
|
||||
|
||||
/** 查询轨迹(按时间段) */
|
||||
public JSONObject queryTrace(String sid, String tid, long start, long end) {
|
||||
return sendRequest(HttpMethod.GET, "/terminal/trsearch",
|
||||
builder -> builder.queryParam("sid", sid)
|
||||
.queryParam("tid", tid)
|
||||
.queryParam("starttime", start)
|
||||
.queryParam("endtime", end), null);
|
||||
}
|
||||
|
||||
/** 删除轨迹 */
|
||||
public JSONObject deleteTrace(Long sid, Long tid, Long trid) {
|
||||
return sendRequest(HttpMethod.POST, "/trace/delete",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("tid", tid).queryParam("trid", trid), null);
|
||||
}
|
||||
|
||||
/* ========================== 4. 轨迹点上传 ========================== */
|
||||
|
||||
/**
|
||||
* 单点/批量上传轨迹点
|
||||
* 注意:上传点的参数较多,且位于 Body 中
|
||||
*/
|
||||
public JSONObject uploadPoints(Long sid, Long tid, Long trid, Object points) {
|
||||
Map<String, Object> bodyMap = new HashMap<>();
|
||||
bodyMap.put("sid", sid);
|
||||
bodyMap.put("tid", tid);
|
||||
bodyMap.put("trid", trid);
|
||||
bodyMap.put("points", points);
|
||||
return sendRequest(HttpMethod.POST, "/point/upload", null, bodyMap);
|
||||
}
|
||||
|
||||
/* ========================== 5. 围栏 ========================== */
|
||||
|
||||
/** 创建圆形围栏 */
|
||||
public JSONObject createCircleFence(String name, String center, int radius, String sid, String... tids) {
|
||||
return sendRequest(HttpMethod.POST, "/geofence/add/circle", builder -> {
|
||||
builder.queryParam("name", name)
|
||||
.queryParam("center", center)
|
||||
.queryParam("radius", radius)
|
||||
.queryParam("sid", sid);
|
||||
if (tids != null && tids.length > 0) {
|
||||
builder.queryParam("tids", String.join(",", tids));
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
/** 创建多边形围栏 */
|
||||
public JSONObject createPolygonFence(String name, String points, String sid, String... tids) {
|
||||
return sendRequest(HttpMethod.POST, "/geofence/add/polygon", builder -> {
|
||||
builder.queryParam("name", name)
|
||||
.queryParam("points", points)
|
||||
.queryParam("sid", sid);
|
||||
if (tids != null && tids.length > 0) {
|
||||
builder.queryParam("tids", String.join(",", tids));
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
/** 绑定/解绑终端到围栏 */
|
||||
public JSONObject bindTerminalToFence(String sid, String gfid, String... tids) {
|
||||
return sendRequest(HttpMethod.POST, "/geofence/terminal/bind",
|
||||
builder -> builder.queryParam("sid", sid)
|
||||
.queryParam("gfid", gfid)
|
||||
.queryParam("tids", String.join(",", tids))
|
||||
.queryParam("enable", "1"), null);
|
||||
}
|
||||
|
||||
/** 查询终端与围栏当前状态(在/不在) */
|
||||
public JSONObject queryTerminalFenceStatus(Long sid, String gfids, Long tid) {
|
||||
return sendRequest(HttpMethod.GET, "/geofence/status/terminal",
|
||||
builder -> {
|
||||
builder.queryParam("sid", sid)
|
||||
.queryParam("tid", tid);
|
||||
if (gfids != null) {
|
||||
builder.queryParam("gfids", gfids);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
/** 删除围栏 */
|
||||
public JSONObject deleteFence(String sid, String gfid) {
|
||||
return sendRequest(HttpMethod.POST, "/geofence/delete",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("gfid", gfid), null);
|
||||
}
|
||||
|
||||
/* ========================== 6. 轨迹纠偏&里程 ========================== */
|
||||
|
||||
/** 轨迹纠偏(驾车) */
|
||||
public JSONObject correctDriving(String sid, String tid, String trid, int gap, int angle, int speed, int accuracy) {
|
||||
return sendRequest(HttpMethod.POST, "/terminal/correct",
|
||||
builder -> builder.queryParam("sid", sid)
|
||||
.queryParam("tid", tid)
|
||||
.queryParam("trid", trid)
|
||||
.queryParam("gap", gap)
|
||||
.queryParam("angle", angle)
|
||||
.queryParam("speed", speed)
|
||||
.queryParam("accuracy", accuracy), null);
|
||||
}
|
||||
|
||||
/** 计算轨迹里程 */
|
||||
public JSONObject calDistance(String sid, String tid, String trid) {
|
||||
return sendRequest(HttpMethod.GET, "/terminal/distance",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("tid", tid).queryParam("trid", trid), null);
|
||||
}
|
||||
|
||||
/* ========================== 7. 空间检索 ========================== */
|
||||
|
||||
/** 圆形范围内终端检索 */
|
||||
public JSONObject searchTerminalInCircle(String sid, String center, int radius) {
|
||||
return sendRequest(HttpMethod.GET, "/terminal/aroundsearch",
|
||||
builder -> builder.queryParam("sid", sid).queryParam("center", center).queryParam("radius", radius), null);
|
||||
}
|
||||
|
||||
/* ========================== 核心请求封装 ========================== */
|
||||
|
||||
/**
|
||||
* 统一请求处理方法
|
||||
*
|
||||
* @param method HTTP方法
|
||||
* @param path API路径
|
||||
* @param paramsConsumer URL参数构建器
|
||||
* @param body Body参数 (POST JSON时使用)
|
||||
* @return 响应JSONObject
|
||||
*/
|
||||
private JSONObject sendRequest(HttpMethod method, String path,
|
||||
Consumer<UriComponentsBuilder> paramsConsumer,
|
||||
Object body) {
|
||||
try {
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(BASE_URL + path)
|
||||
.queryParam("key", key);
|
||||
if (paramsConsumer != null) paramsConsumer.accept(builder);
|
||||
String url = builder.build().encode().toString();
|
||||
|
||||
String result;
|
||||
if (body == null) {
|
||||
result = method == HttpMethod.GET ?
|
||||
HttpUtil.get(url) : HttpUtil.post(url, "");
|
||||
} else {
|
||||
String json = JSONUtil.toJsonStr(body);
|
||||
result = HttpUtil.post(url, json);
|
||||
}
|
||||
|
||||
log.info("Gaode Track Request: {} {}", method, url);
|
||||
|
||||
if (StrUtil.isNotBlank(result)) {
|
||||
JSONObject json = JSONUtil.parseObj(result);
|
||||
if (json.getInt("errcode", -1) != 10000) {
|
||||
log.warn("高德轨迹服务错误: {}", json);
|
||||
}
|
||||
return json;
|
||||
} else {
|
||||
return createErrorJson(-1, "empty response");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("高德轨迹服务异常", e);
|
||||
return createErrorJson(-1, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject createErrorJson(int code, String msg) {
|
||||
JSONObject err = new JSONObject();
|
||||
err.set("errcode", code);
|
||||
err.set("errmsg", msg);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@ -48,8 +48,8 @@
|
||||
r.event_time, r.event_address,
|
||||
r.create_time
|
||||
FROM device_fence_access_record r
|
||||
LEFT JOIN device_geo_fence f ON r.fence_id = f.id
|
||||
LEFT JOIN device d ON r.device_id = d.id
|
||||
INNER JOIN device_geo_fence f ON r.fence_id = f.id
|
||||
INNER JOIN device d ON r.device_id = d.id
|
||||
<where>
|
||||
<if test="bo.fenceId != null">
|
||||
AND r.fence_id = #{bo.fenceId}
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
SELECT
|
||||
da.id AS id, d.id AS deviceId, d.device_name, d.bluetooth_name, d.group_id,
|
||||
d.pub_topic, d.sub_topic, d.device_pic,d.online_status,
|
||||
d.device_mac, d.device_sn, d.update_by,
|
||||
d.device_mac, d.device_sn, d.update_by,d.sid,d.tid,d.trid,
|
||||
d.device_imei, d.update_time, dg.id AS device_type,
|
||||
d.remark, d.binding_status, t.type_name AS typeName,
|
||||
da.assignee_id AS customerId, da.assignee_name AS customerName,
|
||||
@ -86,6 +86,15 @@
|
||||
<if test="criteria.params.beginTime != null and criteria.params.endTime != null">
|
||||
and da.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime}
|
||||
</if>
|
||||
<if test="criteria.sid != null">
|
||||
and d.sid = #{criteria.sid}
|
||||
</if>
|
||||
<if test="criteria.isTid == true">
|
||||
and d.tid is not null
|
||||
</if>
|
||||
<if test="criteria.isTid == false">
|
||||
and d.tid is null
|
||||
</if>
|
||||
<!-- 管理员可以查看所有设备,普通用户只能查看自己的设备 -->
|
||||
<if test="criteria.isAdmin != true">
|
||||
AND da.assignee_id = #{criteria.currentOwnerId}
|
||||
@ -97,6 +106,46 @@
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 分页查询设备终端 -->
|
||||
<select id="findAllTerminal" resultType="com.fuyuanshen.equipment.domain.Device">
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT
|
||||
da.id AS id, d.id AS deviceId, d.device_name, d.bluetooth_name, d.group_id,
|
||||
d.pub_topic, d.sub_topic, d.device_pic,d.online_status,
|
||||
d.device_mac, d.device_sn, d.update_by,d.sid,d.tid,d.trid,
|
||||
d.device_imei, d.update_time, dg.id AS device_type,
|
||||
d.remark, d.binding_status, t.type_name AS typeName, da.create_time AS create_time,
|
||||
ROW_NUMBER() OVER (PARTITION BY d.id ORDER BY da.create_time DESC) AS rn
|
||||
FROM device d
|
||||
LEFT JOIN device_type t ON d.device_type = t.id
|
||||
LEFT JOIN device_type_grants dg ON dg.device_type_id = t.id
|
||||
LEFT JOIN device_assignments da ON da.device_id = d.id
|
||||
LEFT JOIN device_fence_terminal dft ON dft.device_id = d.id
|
||||
where d.sid = #{criteria.sid}
|
||||
<!-- 时间范围等其他条件保持原样 -->
|
||||
<if test="criteria.deviceName != null and criteria.deviceName.trim() != ''">
|
||||
and d.device_name like concat('%', TRIM(#{criteria.deviceName}), '%')
|
||||
</if>
|
||||
<if test="criteria.deviceImei != null and criteria.deviceImei.trim() != ''">
|
||||
and d.device_imei like concat('%', TRIM(#{criteria.deviceImei}), '%')
|
||||
</if>
|
||||
<if test="criteria.isTid == true">
|
||||
and dft.fence_id = #{criteria.fenceId}
|
||||
</if>
|
||||
<if test="criteria.isTid == false">
|
||||
and (dft.fence_id is null or dft.fence_id != #{criteria.fenceId})
|
||||
</if>
|
||||
<!-- 管理员可以查看所有设备,普通用户只能查看自己的设备 -->
|
||||
<if test="criteria.isAdmin != true">
|
||||
AND da.assignee_id = #{criteria.currentOwnerId}
|
||||
AND dg.customer_id = #{criteria.currentOwnerId}
|
||||
</if>
|
||||
) AS ranked
|
||||
WHERE rn = 1
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
|
||||
<select id="findAllDevices" resultType="com.fuyuanshen.equipment.domain.Device">
|
||||
select
|
||||
@ -266,7 +315,7 @@
|
||||
d.binding_status,
|
||||
d.online_status,
|
||||
c.binding_time,
|
||||
d.create_time,
|
||||
d.create_time,d.sid,d.tid,d.trid,
|
||||
ROW_NUMBER() OVER (PARTITION BY d.id ORDER BY d.id) AS row_num
|
||||
from device d
|
||||
inner join device_type dt on d.device_type = dt.id
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper">
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user