Compare commits
27 Commits
main
...
3d1c2f4e56
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d1c2f4e56 | |||
| c480bda112 | |||
| ccadcb8d4e | |||
| 8c636d0484 | |||
| 0c474ae1f3 | |||
| b85664900e | |||
| b8cb663bbf | |||
| 035b24fedd | |||
| e920cfb860 | |||
| 56e86b070d | |||
| 0898855108 | |||
| 92b65ce6df | |||
| 82399cffed | |||
| 5538ac96e5 | |||
| b703f80355 | |||
| b3b249ea07 | |||
| aff424e73b | |||
| dc513a858a | |||
| df5ce7ddd9 | |||
| 2174dfdb4d | |||
| 2800b89e06 | |||
| 637e46c510 | |||
| 31c2158c8e | |||
| c7c21dc358 | |||
| a7e0803b00 | |||
| 55cacbd322 | |||
| 99ec6eaff0 |
64
fys-admin/src/main/java/com/fuyuanshen/Text.java
Normal file
64
fys-admin/src/main/java/com/fuyuanshen/Text.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package com.fuyuanshen;
|
||||||
|
|
||||||
|
import com.fuyuanshen.equipment.utils.AlibabaTTSUtil;
|
||||||
|
import com.fuyuanshen.equipment.utils.AudioProcessUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: 默苍璃
|
||||||
|
* @date: 2025-12-1518:51
|
||||||
|
*/
|
||||||
|
public class Text {
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
String text = "简述人生观的主要内容。\n" +
|
||||||
|
"人生观的主要内容包括以下三个方面:\n" +
|
||||||
|
"1.人生目的:回答“人为什么活着”的根本问题。它规定了人生的方向,是人生观的核心。\n" +
|
||||||
|
"2.人生态度:回答“人应该怎样活着”的问题。它是指人们通过生活实践形成的对人生问题的一种稳定的心理倾向和基本意图。\n" +
|
||||||
|
"3.人生价值:回答“什么样的人生才有意义”的问题。它是指人的生命及其实践活动对于社会和个人所具有的作用和意义。\n" +
|
||||||
|
"人生目的、人生态度和人生价值相互联系、相辅相成,共同构成一个有机整体。\n" +
|
||||||
|
"人生目的是人生观的核心,它决定人生态度和人生价值的方向;人生态度影响人生目的的实现和人生价值的创造;人生价值是衡量人生观正确与否的尺度。";
|
||||||
|
|
||||||
|
AlibabaTTSUtil alibabaTTSUtil = new AlibabaTTSUtil();
|
||||||
|
AudioProcessUtil audioProcessUtil = new AudioProcessUtil();
|
||||||
|
|
||||||
|
byte[] rawPcmData = alibabaTTSUtil.generateStandardPcmData(text);
|
||||||
|
|
||||||
|
// 使用AudioProcessUtil转换成带头44字节 PCM
|
||||||
|
byte[] pcmData = audioProcessUtil.rawPcmToStandardWav(rawPcmData);
|
||||||
|
|
||||||
|
// String savedPath = audioProcessUtil.saveWavToFile(pcmData, "test_output.wav");
|
||||||
|
// if (savedPath != null) {
|
||||||
|
// log.info("测试文件已保存: {}", savedPath);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 保存WAV文件到本地
|
||||||
|
String savedPath = saveByteArrayToFile(pcmData, "人生观.wav");
|
||||||
|
if (savedPath != null) {
|
||||||
|
System.out.println("WAV文件已保存: " + savedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String saveByteArrayToFile(byte[] data, String filename) throws IOException {
|
||||||
|
// 确定保存路径(可以是临时目录或指定目录)
|
||||||
|
String directory = System.getProperty("java.io.tmpdir"); // 使用系统临时目录
|
||||||
|
File dir = new File(directory);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建完整文件路径
|
||||||
|
File file = new File(dir, filename);
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||||
|
fos.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -64,4 +64,5 @@ public class AppVideoController extends BaseController {
|
|||||||
public R<String> extract(@RequestParam("file") MultipartFile file) throws Exception {
|
public R<String> extract(@RequestParam("file") MultipartFile file) throws Exception {
|
||||||
return R.ok("Success",audioProcessService.extract(file));
|
return R.ok("Success",audioProcessService.extract(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,22 +10,43 @@ import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class MqttConfiguration {
|
public class MqttConfiguration {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MqttPropertiesConfig mqttPropertiesConfig;
|
private MqttPropertiesConfig mqttPropertiesConfig;
|
||||||
/** 创建连接工厂 **/
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建连接工厂
|
||||||
|
**/
|
||||||
@Bean
|
@Bean
|
||||||
public MqttPahoClientFactory mqttPahoClientFactory(){
|
public MqttPahoClientFactory mqttPahoClientFactory() {
|
||||||
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
|
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
|
||||||
MqttConnectOptions options = new MqttConnectOptions();
|
MqttConnectOptions options = new MqttConnectOptions();
|
||||||
options.setCleanSession(true); //设置新会话
|
options.setCleanSession(true); // 设置新会话
|
||||||
options.setUserName(mqttPropertiesConfig.getUsername());
|
|
||||||
options.setPassword(mqttPropertiesConfig.getPassword().toCharArray());
|
// 修复用户名为null时的空指针异常
|
||||||
options.setServerURIs(new String[]{mqttPropertiesConfig.getUrl()});
|
String username = mqttPropertiesConfig.getUsername();
|
||||||
|
if (username != null) {
|
||||||
|
options.setUserName(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修复密码为null时的空指针异常
|
||||||
|
String password = mqttPropertiesConfig.getPassword();
|
||||||
|
if (password != null) {
|
||||||
|
options.setPassword(password.toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修复URL为null时的空指针异常
|
||||||
|
String url = mqttPropertiesConfig.getUrl();
|
||||||
|
if (url != null) {
|
||||||
|
options.setServerURIs(new String[]{url});
|
||||||
|
}
|
||||||
|
|
||||||
options.setAutomaticReconnect(true); // 启用自动重连
|
options.setAutomaticReconnect(true); // 启用自动重连
|
||||||
options.setConnectionTimeout(10); // 设置连接超时时间
|
options.setConnectionTimeout(10); // 设置连接超时时间
|
||||||
options.setKeepAliveInterval(60); // 设置心跳间隔
|
options.setKeepAliveInterval(60); // 设置心跳间隔
|
||||||
factory.setConnectionOptions(options);
|
factory.setConnectionOptions(options);
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -39,8 +39,14 @@ public class MqttInboundConfiguration {
|
|||||||
public MessageProducer messageProducer(){
|
public MessageProducer messageProducer(){
|
||||||
// 生成一个不重复的随机数
|
// 生成一个不重复的随机数
|
||||||
String clientId = mqttPropertiesConfig.getSubClientId() + "_" + UUID.fastUUID();
|
String clientId = mqttPropertiesConfig.getSubClientId() + "_" + UUID.fastUUID();
|
||||||
|
// 修复URL为null时的空指针异常
|
||||||
|
String url = mqttPropertiesConfig.getUrl();
|
||||||
|
if (url == null) {
|
||||||
|
throw new IllegalStateException("MQTT服务器URL未配置");
|
||||||
|
}
|
||||||
|
|
||||||
MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapter = new MqttPahoMessageDrivenChannelAdapter(
|
MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapter = new MqttPahoMessageDrivenChannelAdapter(
|
||||||
mqttPropertiesConfig.getUrl(),
|
url,
|
||||||
clientId,
|
clientId,
|
||||||
mqttPahoClientFactory,
|
mqttPahoClientFactory,
|
||||||
mqttPropertiesConfig.getSubTopic().split(",")
|
mqttPropertiesConfig.getSubTopic().split(",")
|
||||||
|
|||||||
@ -32,8 +32,14 @@ public class MqttOutboundConfiguration {
|
|||||||
@ServiceActivator(inputChannel = "mqttOutboundChannel") // 指定处理器针对哪个通道的消息进行处理
|
@ServiceActivator(inputChannel = "mqttOutboundChannel") // 指定处理器针对哪个通道的消息进行处理
|
||||||
public MessageHandler mqttOutboundMessageHandler(){
|
public MessageHandler mqttOutboundMessageHandler(){
|
||||||
String clientId = mqttPropertiesConfig.getPubClientId() + "_" + UUID.fastUUID();
|
String clientId = mqttPropertiesConfig.getPubClientId() + "_" + UUID.fastUUID();
|
||||||
|
// 修复URL为null时的空指针异常
|
||||||
|
String url = mqttPropertiesConfig.getUrl();
|
||||||
|
if (url == null) {
|
||||||
|
throw new IllegalStateException("MQTT服务器URL未配置");
|
||||||
|
}
|
||||||
|
|
||||||
MqttPahoMessageHandler mqttPahoMessageHandler = new MqttPahoMessageHandler(
|
MqttPahoMessageHandler mqttPahoMessageHandler = new MqttPahoMessageHandler(
|
||||||
mqttPropertiesConfig.getUrl(),
|
url,
|
||||||
clientId,
|
clientId,
|
||||||
mqttPahoClientFactory
|
mqttPahoClientFactory
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
package com.fuyuanshen.global.mqtt.rule.xinghan;
|
package com.fuyuanshen.global.mqtt.rule.xinghan;
|
||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
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 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.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fuyuanshen.common.core.constant.GlobalConstants;
|
import com.fuyuanshen.common.core.constant.GlobalConstants;
|
||||||
import com.fuyuanshen.common.core.utils.StringUtils;
|
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.json.utils.JsonUtils;
|
||||||
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
||||||
import com.fuyuanshen.equipment.domain.Device;
|
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.DeviceAlarmBo;
|
||||||
|
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
|
||||||
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
|
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.DeviceAlarmMapper;
|
||||||
|
import com.fuyuanshen.equipment.mapper.DeviceFenceAccessRecordMapper;
|
||||||
|
import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper;
|
||||||
import com.fuyuanshen.equipment.service.DeviceService;
|
import com.fuyuanshen.equipment.service.DeviceService;
|
||||||
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
|
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.GetAddressFromLatUtil;
|
||||||
import com.fuyuanshen.equipment.utils.map.LngLonUtil;
|
import com.fuyuanshen.equipment.utils.map.LngLonUtil;
|
||||||
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
|
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
|
||||||
@ -30,13 +44,16 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.redisson.api.RLock;
|
import org.redisson.api.RLock;
|
||||||
import org.redisson.api.RedissonClient;
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.FUNCTION_ACCESS_KEY;
|
||||||
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_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 IDeviceAlarmService deviceAlarmService;
|
||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
private final DeviceAlarmMapper deviceAlarmMapper;
|
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
|
@Override
|
||||||
public void execute(MqttRuleContext context) {
|
public void execute(MqttRuleContext context) {
|
||||||
@ -127,16 +150,20 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
|||||||
* 入口:保存报警(SOS 与 Shake 完全独立)
|
* 入口:保存报警(SOS 与 Shake 完全独立)
|
||||||
*/
|
*/
|
||||||
public void saveAlarm(String deviceImei, MqttXinghanJson status) {
|
public void saveAlarm(String deviceImei, MqttXinghanJson status) {
|
||||||
int sos = Optional.ofNullable(status.getStaSOSGrade()).orElse(0);
|
try {
|
||||||
// 1. 处理 SOS 报警
|
int sos = Optional.ofNullable(status.getStaSOSGrade()).orElse(0);
|
||||||
handleSingleAlarm(deviceImei,
|
// 1. 处理 SOS 报警
|
||||||
sos > 0,
|
handleSingleAlarm(deviceImei,
|
||||||
AlarmTypeEnum.SOS);
|
sos > 0,
|
||||||
int shake = Optional.ofNullable(status.getStaShakeBit()).orElse(0);
|
AlarmTypeEnum.SOS);
|
||||||
// 2. 处理 Shake 报警
|
int shake = Optional.ofNullable(status.getStaShakeBit()).orElse(0);
|
||||||
handleSingleAlarm(deviceImei,
|
// 2. 处理 Shake 报警
|
||||||
shake > 0,
|
handleSingleAlarm(deviceImei,
|
||||||
AlarmTypeEnum.SHAKE);
|
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(
|
String location = RedisUtils.getCacheObject(
|
||||||
GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX);
|
GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX);
|
||||||
if (StrUtil.isNotBlank(location)) {
|
if (StrUtil.isNotBlank(location)) {
|
||||||
bo.setLocation(JSONObject.parseObject(location).getString("address"));
|
bo.setLocation(com.alibaba.fastjson2.JSONObject.parseObject(location).getString("address"));
|
||||||
}
|
}
|
||||||
return bo;
|
return bo;
|
||||||
}
|
}
|
||||||
@ -259,63 +286,312 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
|||||||
public void asyncSendLocationToRedisWithFuture(String deviceImei, String latitude, String longitude) {
|
public void asyncSendLocationToRedisWithFuture(String deviceImei, String latitude, String longitude) {
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
try {
|
try {
|
||||||
if(StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)){
|
if (StringUtils.isAnyBlank(deviceImei, latitude, longitude)) {
|
||||||
|
log.warn("位置上报参数为空,deviceImei={}", deviceImei);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//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;
|
return;
|
||||||
}
|
}
|
||||||
String[] latArr = latitude.split("\\.");
|
|
||||||
String[] lonArr = longitude.split("\\.");
|
|
||||||
// 将位置信息存储到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 cacheLatitude = jsonOBj.getString("wgs84_latitude");
|
// 2. 读取 Redis 中缓存的上一次位置
|
||||||
String cacheLongitude = jsonOBj.getString("wgs84_longitude");
|
String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX;
|
||||||
String[] latArr1 = cacheLatitude.split("\\.");
|
String cachedJson = RedisUtils.getCacheObject(redisKey);
|
||||||
String[] lonArr1 = cacheLongitude.split("\\.");
|
|
||||||
|
|
||||||
String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4);
|
if (StringUtils.isNotBlank(cachedJson)) {
|
||||||
String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4);
|
com.alibaba.fastjson2.JSONObject cachedObj = com.alibaba.fastjson2.JSONObject.parseObject(cachedJson);
|
||||||
if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){
|
String cachedWgs84Lat = cachedObj.getString("wgs84_latitude");
|
||||||
log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
|
String cachedWgs84Lon = cachedObj.getString("wgs84_longitude");
|
||||||
return;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造位置信息对象
|
// 3. 位置有明显变化,执行保存
|
||||||
Map<String, Object> locationInfo = new LinkedHashMap<>();
|
doSaveLocation(deviceImei, latitude, longitude);
|
||||||
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());
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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) {
|
} 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)
|
* 存储设备30天历史轨迹到Redis (使用Sorted Set)
|
||||||
*/
|
*/
|
||||||
@ -358,4 +634,6 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,7 +110,7 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule {
|
|||||||
dto.setMessage(String.format("%s设备已收到通知!", latestLog.getDeviceName()));
|
dto.setMessage(String.format("%s设备已收到通知!", latestLog.getDeviceName()));
|
||||||
dto.setUserIds(List.of(latestLog.getCreateBy()));
|
dto.setUserIds(List.of(latestLog.getCreateBy()));
|
||||||
SseMessageUtils.publishMessage(dto);
|
SseMessageUtils.publishMessage(dto);
|
||||||
}, 5, TimeUnit.SECONDS);
|
}, 2, TimeUnit.SECONDS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 1. cover! —— 成功标记
|
// 1. cover! —— 成功标记
|
||||||
|
|||||||
@ -157,4 +157,4 @@ public class CaptchaController {
|
|||||||
return captchaVo;
|
return captchaVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -59,6 +59,7 @@ public class DeviceFenceAccessRecordController extends BaseController {
|
|||||||
ExcelUtil.exportExcel(list, "围栏进出记录", DeviceFenceAccessRecordVo.class, response);
|
ExcelUtil.exportExcel(list, "围栏进出记录", DeviceFenceAccessRecordVo.class, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取围栏进出记录详细信息
|
* 获取围栏进出记录详细信息
|
||||||
*
|
*
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.fuyuanshen.web.controller.device.fence;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
|
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.dto.FenceCheckResponse;
|
||||||
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
||||||
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
||||||
@ -102,6 +103,7 @@ public class DeviceGeoFenceController extends BaseController {
|
|||||||
return toAjax(deviceGeoFenceService.updateByBo(bo));
|
return toAjax(deviceGeoFenceService.updateByBo(bo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除电子围栏
|
* 删除电子围栏
|
||||||
*
|
*
|
||||||
@ -129,4 +131,26 @@ public class DeviceGeoFenceController extends BaseController {
|
|||||||
return ResponseEntity.ok(response);
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,8 @@ spring.boot.admin.client:
|
|||||||
metadata:
|
metadata:
|
||||||
username: ${spring.boot.admin.client.username}
|
username: ${spring.boot.admin.client.username}
|
||||||
userpassword: ${spring.boot.admin.client.password}
|
userpassword: ${spring.boot.admin.client.password}
|
||||||
username: @monitor.username@
|
username: ${monitor.username}
|
||||||
password: @monitor.password@
|
password: ${monitor.password}
|
||||||
|
|
||||||
--- # snail-job 配置
|
--- # snail-job 配置
|
||||||
snail-job:
|
snail-job:
|
||||||
|
|||||||
@ -11,8 +11,8 @@ spring.boot.admin.client:
|
|||||||
metadata:
|
metadata:
|
||||||
username: ${spring.boot.admin.client.username}
|
username: ${spring.boot.admin.client.username}
|
||||||
userpassword: ${spring.boot.admin.client.password}
|
userpassword: ${spring.boot.admin.client.password}
|
||||||
username: @monitor.username@
|
username: ${monitor.username}
|
||||||
password: @monitor.password@
|
password: ${monitor.password}
|
||||||
|
|
||||||
--- # snail-job 配置
|
--- # snail-job 配置
|
||||||
snail-job:
|
snail-job:
|
||||||
|
|||||||
@ -132,6 +132,8 @@ tenant:
|
|||||||
- app_menu
|
- app_menu
|
||||||
- app_user_role
|
- app_user_role
|
||||||
- app_role_menu
|
- app_role_menu
|
||||||
|
- track_service
|
||||||
|
- device_fence_terminal
|
||||||
|
|
||||||
# MyBatisPlus配置
|
# MyBatisPlus配置
|
||||||
# https://baomidou.com/config/
|
# https://baomidou.com/config/
|
||||||
|
|||||||
@ -39,8 +39,8 @@ public class EncryptUtilsTest {
|
|||||||
loginBody.setClientId("e5cd7e4891bf95d1d19206ce24a7b32e");
|
loginBody.setClientId("e5cd7e4891bf95d1d19206ce24a7b32e");
|
||||||
loginBody.setGrantType("password");
|
loginBody.setGrantType("password");
|
||||||
loginBody.setTenantId("894078");
|
loginBody.setTenantId("894078");
|
||||||
loginBody.setCode("0");
|
loginBody.setCode("15");
|
||||||
loginBody.setUuid("1d6615668c7f410da77c4e002c601073");
|
loginBody.setUuid("28ecf3d396ce4e6db8eb414992235fad");
|
||||||
// loginBody.setUsername("admin");
|
// loginBody.setUsername("admin");
|
||||||
// loginBody.setPassword("admin123");
|
// loginBody.setPassword("admin123");
|
||||||
loginBody.setUsername("dyf");
|
loginBody.setUsername("dyf");
|
||||||
|
|||||||
@ -72,6 +72,12 @@ public class DeviceController extends BaseController {
|
|||||||
return deviceService.queryAll(criteria, page);
|
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("新增设备")
|
// @Log("新增设备")
|
||||||
@Operation(summary = "新增设备")
|
@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;
|
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.baomidou.mybatisplus.annotation.*;
|
||||||
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
|
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
|
||||||
|
import com.fuyuanshen.common.tenant.core.TenantEntity;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -18,7 +19,7 @@ import java.io.Serial;
|
|||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@TableName("device_fence_access_record")
|
@TableName("device_fence_access_record")
|
||||||
public class DeviceFenceAccessRecord extends BaseEntity {
|
public class DeviceFenceAccessRecord extends TenantEntity {
|
||||||
|
|
||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
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;
|
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;
|
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
|
* online_status
|
||||||
*/
|
*/
|
||||||
private Integer onlineStatus;
|
private Integer onlineStatus;
|
||||||
|
/**
|
||||||
|
* 高德终端ID是否存在
|
||||||
|
*/
|
||||||
|
private Boolean isTid;
|
||||||
|
/**
|
||||||
|
* 高德服务ID
|
||||||
|
*/
|
||||||
|
private String sid;
|
||||||
|
/**
|
||||||
|
* 围栏ID
|
||||||
|
*/
|
||||||
|
private Long fenceId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绑定状态
|
* 绑定状态
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.fuyuanshen.equipment.domain.vo;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
import cn.idev.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
@ -60,6 +61,13 @@ public class DeviceFenceAccessRecordVo implements Serializable {
|
|||||||
@ExcelProperty(value = "设备名称")
|
@ExcelProperty(value = "设备名称")
|
||||||
private String deviceName;
|
private String deviceName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件时间
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "事件时间")
|
||||||
|
@ColumnWidth(120)
|
||||||
|
private String eventTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户ID
|
* 用户ID
|
||||||
*/
|
*/
|
||||||
@ -76,27 +84,21 @@ public class DeviceFenceAccessRecordVo implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 纬度
|
* 纬度
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "纬度")
|
// @ExcelProperty(value = "纬度")
|
||||||
private Double latitude;
|
private Double latitude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 经度
|
* 经度
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "经度")
|
// @ExcelProperty(value = "经度")
|
||||||
private Double longitude;
|
private Double longitude;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定位精度
|
* 定位精度
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "定位精度")
|
// @ExcelProperty(value = "定位精度")
|
||||||
private Long accuracy;
|
private Long accuracy;
|
||||||
|
|
||||||
/**
|
|
||||||
* 事件时间
|
|
||||||
*/
|
|
||||||
@ExcelProperty(value = "事件时间")
|
|
||||||
private Date eventTime;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 事件地址
|
* 事件地址
|
||||||
*/
|
*/
|
||||||
@ -109,5 +111,4 @@ public class DeviceFenceAccessRecordVo implements Serializable {
|
|||||||
@ExcelProperty(value = "记录创建时间")
|
@ExcelProperty(value = "记录创建时间")
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,6 +101,14 @@ public class DeviceGeoFenceVo implements Serializable {
|
|||||||
*/
|
*/
|
||||||
// @ExcelProperty(value = "更新时间")
|
// @ExcelProperty(value = "更新时间")
|
||||||
private Date updateTime;
|
private Date updateTime;
|
||||||
|
/**
|
||||||
|
* 高德服务ID
|
||||||
|
*/
|
||||||
|
private Long sid;
|
||||||
|
/**
|
||||||
|
* 高德围栏ID
|
||||||
|
*/
|
||||||
|
private Long gfid;
|
||||||
|
|
||||||
|
|
||||||
public void setAreaTypeNameByAreaType() {
|
public void setAreaTypeNameByAreaType() {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package com.fuyuanshen.equipment.domain.vo;
|
package com.fuyuanshen.equipment.domain.vo;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fuyuanshen.common.tenant.core.TenantEntity;
|
import com.fuyuanshen.common.tenant.core.TenantEntity;
|
||||||
import com.fuyuanshen.equipment.domain.DeviceRepairRecords;
|
import com.fuyuanshen.equipment.domain.DeviceRepairRecords;
|
||||||
@ -34,31 +36,39 @@ public class DeviceRepairRecordsVo extends TenantEntity implements Serializable
|
|||||||
/**
|
/**
|
||||||
* 维修记录ID
|
* 维修记录ID
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "维修记录ID")
|
// @ExcelProperty(value = "维修记录ID")
|
||||||
private Long recordId;
|
private Long recordId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备ID
|
* 设备ID
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "设备ID")
|
// @ExcelProperty(value = "设备ID")
|
||||||
private String deviceId;
|
private String deviceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备名称
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "设备名称")
|
||||||
|
private String deviceName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 维修时间
|
* 维修时间
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "维修时间")
|
@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;
|
private String repairPart;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 维修原因
|
* 损坏原因
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "维修原因")
|
@ExcelProperty(value = "损坏原因")
|
||||||
private String repairReason;
|
private String repairReason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,12 +76,8 @@ public class DeviceRepairRecordsVo extends TenantEntity implements Serializable
|
|||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "维修人员")
|
@ExcelProperty(value = "维修人员")
|
||||||
private String repairPerson;
|
private String repairPerson;
|
||||||
/**
|
|
||||||
* 维修人员
|
|
||||||
*/
|
|
||||||
@ExcelProperty(value = "设备名称")
|
|
||||||
private String deviceName;
|
|
||||||
|
|
||||||
private List<DeviceRepairImagesVo> images;
|
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;
|
package com.fuyuanshen.equipment.domain.vo;
|
||||||
|
|
||||||
import cn.idev.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@ -103,5 +104,20 @@ public class WebDeviceVo implements Serializable {
|
|||||||
* 分享用户数量
|
* 分享用户数量
|
||||||
*/
|
*/
|
||||||
private Integer shareUsersNumber;
|
private Integer shareUsersNumber;
|
||||||
|
/**
|
||||||
|
* 高德服务ID
|
||||||
|
*/
|
||||||
|
@Schema(title = "服务ID(高德)")
|
||||||
|
private Long sid;
|
||||||
|
/**
|
||||||
|
* 高德终端ID
|
||||||
|
*/
|
||||||
|
@Schema(title = "终端ID(高德)")
|
||||||
|
private Long tid;
|
||||||
|
/**
|
||||||
|
* 高德轨迹ID
|
||||||
|
*/
|
||||||
|
@Schema(title = "轨迹ID(高德)")
|
||||||
|
private Long trid;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,17 +28,19 @@ public interface DeviceFenceAccessRecordMapper extends BaseMapperPlus<DeviceFenc
|
|||||||
*/
|
*/
|
||||||
Page<DeviceFenceAccessRecordVo> selectVoPageWithFenceAndDeviceName(Page<DeviceFenceAccessRecord> page, @Param(Constants.WRAPPER) Wrapper<DeviceFenceAccessRecord> wrapper);
|
Page<DeviceFenceAccessRecordVo> selectVoPageWithFenceAndDeviceName(Page<DeviceFenceAccessRecord> page, @Param(Constants.WRAPPER) Wrapper<DeviceFenceAccessRecord> wrapper);
|
||||||
|
|
||||||
List<DeviceFenceAccessRecordVo> selectVoPageWithFenceAndDeviceName(@Param(Constants.WRAPPER) Wrapper<DeviceFenceAccessRecord> wrapper,@Param("fenceName") String fenceName);
|
List<DeviceFenceAccessRecordVo> selectVoPageWithFenceAndDeviceName(@Param(Constants.WRAPPER) Wrapper<DeviceFenceAccessRecord> wrapper, @Param("fenceName") String fenceName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询围栏进出记录列表(纯XML形式)
|
* 分页查询围栏进出记录列表(纯XML形式)
|
||||||
*
|
*
|
||||||
* @param page 分页参数
|
* @param page 分页参数
|
||||||
* @param bo 查询条件
|
* @param bo 查询条件
|
||||||
* @return 围栏进出记录分页列表
|
* @return 围栏进出记录分页列表
|
||||||
*/
|
*/
|
||||||
Page<DeviceFenceAccessRecordVo> selectVoPageByXml(Page<DeviceFenceAccessRecord> page, @Param("bo") DeviceFenceAccessRecordBo bo);
|
Page<DeviceFenceAccessRecordVo> selectVoPageByXml(Page<DeviceFenceAccessRecord> page, @Param("bo") DeviceFenceAccessRecordBo bo);
|
||||||
|
|
||||||
|
List<DeviceFenceAccessRecordVo> selectVoPageByXml(@Param("bo") DeviceFenceAccessRecordBo bo);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询设备最新的围栏记录
|
* 查询设备最新的围栏记录
|
||||||
|
|||||||
@ -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);
|
List<Device> findAll(@Param("criteria") DeviceQueryCriteria criteria);
|
||||||
|
|
||||||
|
IPage<Device> findAllTerminal(@Param("criteria") DeviceQueryCriteria criteria, Page<Device> page);
|
||||||
|
|
||||||
List<Device> findAllDevices(@Param("criteria") DeviceQueryCriteria criteria);
|
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> 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.Device;
|
||||||
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
|
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
|
||||||
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
|
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.dto.FenceCheckResponse;
|
||||||
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
||||||
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
||||||
@ -78,4 +79,8 @@ public interface IDeviceGeoFenceService extends IService<DeviceGeoFence> {
|
|||||||
* @return 位置检查结果
|
* @return 位置检查结果
|
||||||
*/
|
*/
|
||||||
FenceCheckResponse checkPosition(FenceCheckRequest request);
|
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);
|
||||||
|
}
|
||||||
@ -33,6 +33,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec
|
|||||||
|
|
||||||
private final DeviceFenceAccessRecordMapper baseMapper;
|
private final DeviceFenceAccessRecordMapper baseMapper;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询围栏进出记录
|
* 查询围栏进出记录
|
||||||
*
|
*
|
||||||
@ -68,8 +69,8 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<DeviceFenceAccessRecordVo> queryList(DeviceFenceAccessRecordBo bo) {
|
public List<DeviceFenceAccessRecordVo> queryList(DeviceFenceAccessRecordBo bo) {
|
||||||
LambdaQueryWrapper<DeviceFenceAccessRecord> lqw = buildQueryWrapper(bo);
|
// LambdaQueryWrapper<DeviceFenceAccessRecord> lqw = buildQueryWrapper(bo);
|
||||||
return baseMapper.selectVoPageWithFenceAndDeviceName(lqw, bo.getFenceName());
|
return baseMapper.selectVoPageByXml(bo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.fuyuanshen.equipment.service.impl;
|
package com.fuyuanshen.equipment.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
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.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.fuyuanshen.equipment.domain.Device;
|
import com.fuyuanshen.equipment.domain.Device;
|
||||||
|
import com.fuyuanshen.equipment.domain.DeviceFenceTerminal;
|
||||||
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
|
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
|
||||||
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
|
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
|
||||||
import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo;
|
import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo;
|
||||||
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
|
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.dto.FenceCheckResponse;
|
||||||
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
|
||||||
import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo;
|
import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo;
|
||||||
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
|
||||||
|
import com.fuyuanshen.equipment.mapper.DeviceFenceTerminalMapper;
|
||||||
import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper;
|
import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper;
|
||||||
import com.fuyuanshen.equipment.mapper.DeviceMapper;
|
import com.fuyuanshen.equipment.mapper.DeviceMapper;
|
||||||
import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService;
|
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.service.IDeviceGeoFenceService;
|
||||||
import com.fuyuanshen.equipment.utils.map.GeoFenceChecker;
|
import com.fuyuanshen.equipment.utils.map.GeoFenceChecker;
|
||||||
import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil;
|
import com.fuyuanshen.equipment.utils.map.GetAddressFromLatUtil;
|
||||||
|
import com.fuyuanshen.system.domain.SysRoleMenu;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -48,6 +53,7 @@ public class DeviceGeoFenceServiceImpl extends ServiceImpl<DeviceGeoFenceMapper
|
|||||||
private final IDeviceFenceStatusService fenceStatusService; // 添加此行
|
private final IDeviceFenceStatusService fenceStatusService; // 添加此行
|
||||||
|
|
||||||
private final IDeviceFenceAccessRecordService fenceAccessRecordService; // 添加此行
|
private final IDeviceFenceAccessRecordService fenceAccessRecordService; // 添加此行
|
||||||
|
private final DeviceFenceTerminalMapper deviceFenceTerminalMapper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -270,4 +276,32 @@ public class DeviceGeoFenceServiceImpl extends ServiceImpl<DeviceGeoFenceMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@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());
|
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
|
@Override
|
||||||
public List<Device> queryAll(DeviceQueryCriteria criteria) {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,27 +30,45 @@ import static cn.dev33.satoken.SaManager.log;
|
|||||||
@Component
|
@Component
|
||||||
public class AlibabaTTSUtil {
|
public class AlibabaTTSUtil {
|
||||||
// ========== 常量配置 ==========
|
// ========== 常量配置 ==========
|
||||||
/** 阿里云TTS服务基础URL */
|
/**
|
||||||
|
* 阿里云TTS服务基础URL
|
||||||
|
*/
|
||||||
private static final String BASE_URL = "https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/tts";
|
private static final String BASE_URL = "https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/tts";
|
||||||
/** 音频内容类型标识 */
|
/**
|
||||||
|
* 音频内容类型标识
|
||||||
|
*/
|
||||||
private static final String CONTENT_TYPE_AUDIO = "audio/mpeg";
|
private static final String CONTENT_TYPE_AUDIO = "audio/mpeg";
|
||||||
|
|
||||||
// ========== 默认参数值 ==========
|
// ========== 默认参数值 ==========
|
||||||
/** 默认发音人 - 小云 */
|
/**
|
||||||
|
* 默认发音人 - 小云
|
||||||
|
*/
|
||||||
private static final String DEFAULT_VOICE = "xiaoyun";
|
private static final String DEFAULT_VOICE = "xiaoyun";
|
||||||
/** 默认音量 50% */
|
/**
|
||||||
|
* 默认音量 50%
|
||||||
|
*/
|
||||||
private static final int DEFAULT_VOLUME = 50;
|
private static final int DEFAULT_VOLUME = 50;
|
||||||
/** 默认语速 0(正常) */
|
/**
|
||||||
|
* 默认语速 0(正常)
|
||||||
|
*/
|
||||||
private static final int DEFAULT_SPEECH_RATE = 1;
|
private static final int DEFAULT_SPEECH_RATE = 1;
|
||||||
/** 默认语调 0(正常) */
|
/**
|
||||||
|
* 默认语调 0(正常)
|
||||||
|
*/
|
||||||
private static final int DEFAULT_PITCH_RATE = 0;
|
private static final int DEFAULT_PITCH_RATE = 0;
|
||||||
/** 默认音频格式 pcm */
|
/**
|
||||||
|
* 默认音频格式 pcm
|
||||||
|
*/
|
||||||
private static final String DEFAULT_FORMAT = "pcm";
|
private static final String DEFAULT_FORMAT = "pcm";
|
||||||
/** 默认采样率 16000Hz */
|
/**
|
||||||
|
* 默认采样率 16000Hz
|
||||||
|
*/
|
||||||
private static final int DEFAULT_SAMPLE_RATE = 16000;
|
private static final int DEFAULT_SAMPLE_RATE = 16000;
|
||||||
|
|
||||||
// ========== Token管理配置 ==========
|
// ========== Token管理配置 ==========
|
||||||
/** Token刷新缓冲时间(提前5分钟刷新,单位:毫秒) */
|
/**
|
||||||
|
* Token刷新缓冲时间(提前5分钟刷新,单位:毫秒)
|
||||||
|
*/
|
||||||
private static final long TOKEN_REFRESH_BUFFER = 5 * 60 * 1000L;
|
private static final long TOKEN_REFRESH_BUFFER = 5 * 60 * 1000L;
|
||||||
|
|
||||||
// ========== 配置参数(从配置文件读取) ==========
|
// ========== 配置参数(从配置文件读取) ==========
|
||||||
@ -70,7 +88,8 @@ public class AlibabaTTSUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成语音文件 - 简化版(使用默认参数)
|
* 生成语音文件 - 简化版(使用默认参数)
|
||||||
* @param text 要转换的文本内容
|
*
|
||||||
|
* @param text 要转换的文本内容
|
||||||
* @param audioSaveFile 音频文件保存路径
|
* @param audioSaveFile 音频文件保存路径
|
||||||
* @return true-成功 false-失败
|
* @return true-成功 false-失败
|
||||||
*/
|
*/
|
||||||
@ -80,11 +99,12 @@ public class AlibabaTTSUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成语音文件 - 标准版
|
* 生成语音文件 - 标准版
|
||||||
* @param text 要转换的文本内容
|
*
|
||||||
|
* @param text 要转换的文本内容
|
||||||
* @param audioSaveFile 音频文件保存路径
|
* @param audioSaveFile 音频文件保存路径
|
||||||
* @param format 音频格式(如:mp3, wav等)
|
* @param format 音频格式(如:mp3, wav等)
|
||||||
* @param sampleRate 采样率(如:16000, 22050, 44100等)
|
* @param sampleRate 采样率(如:16000, 22050, 44100等)
|
||||||
* @param voice 发音人(如:xiaoyun, xiaoqian等)
|
* @param voice 发音人(如:xiaoyun, xiaoqian等)
|
||||||
* @return true-成功 false-失败
|
* @return true-成功 false-失败
|
||||||
*/
|
*/
|
||||||
public boolean generateSpeech(String text, String audioSaveFile, String format,
|
public boolean generateSpeech(String text, String audioSaveFile, String format,
|
||||||
@ -95,14 +115,15 @@ public class AlibabaTTSUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成语音文件 - 完整版(支持所有参数调节)
|
* 生成语音文件 - 完整版(支持所有参数调节)
|
||||||
* @param text 要转换的文本内容
|
*
|
||||||
|
* @param text 要转换的文本内容
|
||||||
* @param audioSaveFile 音频文件保存路径
|
* @param audioSaveFile 音频文件保存路径
|
||||||
* @param format 音频格式
|
* @param format 音频格式
|
||||||
* @param sampleRate 采样率
|
* @param sampleRate 采样率
|
||||||
* @param voice 发音人
|
* @param voice 发音人
|
||||||
* @param volume 音量(0-100)
|
* @param volume 音量(0-100)
|
||||||
* @param speechRate 语速(-500~500)
|
* @param speechRate 语速(-500~500)
|
||||||
* @param pitchRate 语调(-500~500)
|
* @param pitchRate 语调(-500~500)
|
||||||
* @return true-成功 false-失败
|
* @return true-成功 false-失败
|
||||||
*/
|
*/
|
||||||
public boolean generateSpeech(String text, String audioSaveFile, String format,
|
public boolean generateSpeech(String text, String audioSaveFile, String format,
|
||||||
@ -158,6 +179,7 @@ public class AlibabaTTSUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取有效的访问令牌(优先从缓存获取,缓存不存在则重新生成)
|
* 获取有效的访问令牌(优先从缓存获取,缓存不存在则重新生成)
|
||||||
|
*
|
||||||
* @return 访问令牌,获取失败返回null
|
* @return 访问令牌,获取失败返回null
|
||||||
*/
|
*/
|
||||||
private String getValidAccessToken() {
|
private String getValidAccessToken() {
|
||||||
@ -181,6 +203,7 @@ public class AlibabaTTSUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新访问令牌(调用阿里云API获取新令牌并缓存)
|
* 刷新访问令牌(调用阿里云API获取新令牌并缓存)
|
||||||
|
*
|
||||||
* @return 新的访问令牌,获取失败返回null
|
* @return 新的访问令牌,获取失败返回null
|
||||||
*/
|
*/
|
||||||
private String refreshAccessToken() {
|
private String refreshAccessToken() {
|
||||||
@ -202,6 +225,7 @@ public class AlibabaTTSUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 参数验证
|
* 参数验证
|
||||||
|
*
|
||||||
* @throws IllegalArgumentException 参数不合法时抛出异常
|
* @throws IllegalArgumentException 参数不合法时抛出异常
|
||||||
*/
|
*/
|
||||||
private void validateParameters(String text, String audioSaveFile, String format, int sampleRate) {
|
private void validateParameters(String text, String audioSaveFile, String format, int sampleRate) {
|
||||||
@ -248,10 +272,10 @@ public class AlibabaTTSUtil {
|
|||||||
"&text=" + encodedText +
|
"&text=" + encodedText +
|
||||||
"&format=" + format +
|
"&format=" + format +
|
||||||
"&sample_rate=" + sampleRate;
|
"&sample_rate=" + sampleRate;
|
||||||
//"&voice=" + actualVoice +
|
//"&voice=" + actualVoice +
|
||||||
//"&volume=" + Math.max(0, Math.min(100, volume)) + // 音量范围限制
|
//"&volume=" + Math.max(0, Math.min(100, volume)) + // 音量范围限制
|
||||||
//"&speech_rate=" + Math.max(-500, Math.min(500, speechRate)) + // 语速范围限制
|
//"&speech_rate=" + Math.max(-500, Math.min(500, speechRate)) + // 语速范围限制
|
||||||
//"&pitch_rate=" + Math.max(-500, Math.min(500, pitchRate)); // 语调范围限制
|
//"&pitch_rate=" + Math.max(-500, Math.min(500, pitchRate)); // 语调范围限制
|
||||||
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new RuntimeException("UTF-8编码不支持", e);
|
throw new RuntimeException("UTF-8编码不支持", e);
|
||||||
|
|||||||
@ -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.event_time, r.event_address,
|
||||||
r.create_time
|
r.create_time
|
||||||
FROM device_fence_access_record r
|
FROM device_fence_access_record r
|
||||||
LEFT JOIN device_geo_fence f ON r.fence_id = f.id
|
INNER JOIN device_geo_fence f ON r.fence_id = f.id
|
||||||
LEFT JOIN device d ON r.device_id = d.id
|
INNER JOIN device d ON r.device_id = d.id
|
||||||
<where>
|
<where>
|
||||||
<if test="bo.fenceId != null">
|
<if test="bo.fenceId != null">
|
||||||
AND r.fence_id = #{bo.fenceId}
|
AND r.fence_id = #{bo.fenceId}
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
SELECT
|
SELECT
|
||||||
da.id AS id, d.id AS deviceId, d.device_name, d.bluetooth_name, d.group_id,
|
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.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.device_imei, d.update_time, dg.id AS device_type,
|
||||||
d.remark, d.binding_status, t.type_name AS typeName,
|
d.remark, d.binding_status, t.type_name AS typeName,
|
||||||
da.assignee_id AS customerId, da.assignee_name AS customerName,
|
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">
|
<if test="criteria.params.beginTime != null and criteria.params.endTime != null">
|
||||||
and da.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime}
|
and da.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime}
|
||||||
</if>
|
</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">
|
<if test="criteria.isAdmin != true">
|
||||||
AND da.assignee_id = #{criteria.currentOwnerId}
|
AND da.assignee_id = #{criteria.currentOwnerId}
|
||||||
@ -97,6 +106,46 @@
|
|||||||
ORDER BY create_time DESC
|
ORDER BY create_time DESC
|
||||||
</select>
|
</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 id="findAllDevices" resultType="com.fuyuanshen.equipment.domain.Device">
|
||||||
select
|
select
|
||||||
@ -266,7 +315,7 @@
|
|||||||
d.binding_status,
|
d.binding_status,
|
||||||
d.online_status,
|
d.online_status,
|
||||||
c.binding_time,
|
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
|
ROW_NUMBER() OVER (PARTITION BY d.id ORDER BY d.id) AS row_num
|
||||||
from device d
|
from device d
|
||||||
inner join device_type dt on d.device_type = dt.id
|
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>
|
||||||
16
pom.xml
16
pom.xml
@ -85,10 +85,10 @@
|
|||||||
<monitor.username>fys</monitor.username>
|
<monitor.username>fys</monitor.username>
|
||||||
<monitor.password>123456</monitor.password>
|
<monitor.password>123456</monitor.password>
|
||||||
</properties>
|
</properties>
|
||||||
<activation>
|
<!-- <activation> -->
|
||||||
<!-- 默认环境 -->
|
<!-- <!– 默认环境 –> -->
|
||||||
<activeByDefault>true</activeByDefault>
|
<!-- <activeByDefault>true</activeByDefault> -->
|
||||||
</activation>
|
<!-- </activation> -->
|
||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>prod</id>
|
<id>prod</id>
|
||||||
@ -98,10 +98,10 @@
|
|||||||
<monitor.username>fys</monitor.username>
|
<monitor.username>fys</monitor.username>
|
||||||
<monitor.password>123456</monitor.password>
|
<monitor.password>123456</monitor.password>
|
||||||
</properties>
|
</properties>
|
||||||
<!-- <activation> -->
|
<activation>
|
||||||
<!-- <!– 默认环境 –> -->
|
<!-- 默认环境 -->
|
||||||
<!-- <activeByDefault>true</activeByDefault> -->
|
<activeByDefault>true</activeByDefault>
|
||||||
<!-- </activation> -->
|
</activation>
|
||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>jingquan</id>
|
<id>jingquan</id>
|
||||||
|
|||||||
Reference in New Issue
Block a user