Compare commits

23 Commits

Author SHA1 Message Date
2d59397de5 大屏数据 2025-09-27 15:30:12 +08:00
b7c81419a4 围栏进出记录-导出-报错 2025-09-26 11:48:20 +08:00
3859c20081 Merge branch 'dyf-device' into 6170 2025-09-26 09:33:07 +08:00
3bdabb04e2 设备列表-导出excel 缺少绑定状态,excel表头顺序按列表展示 2025-09-26 09:32:42 +08:00
e124694e67 Merge branch 'main' into dyf-device 2025-09-26 08:36:00 +08:00
dyf
e528cd04e9 Merge pull request 'jingquan' (#12) from liwenlong/fys-Multi-tenant:jingquan into main
Reviewed-on: #12
2025-09-26 08:34:05 +08:00
735199b9e0 控制中心查询3 2025-09-25 10:58:57 +08:00
a69c665d8b 控制中心查询2 2025-09-25 10:58:25 +08:00
5a52129fd0 控制中心查询 2025-09-25 09:21:11 +08:00
7eb5e6095a feat(device): 新增报警类型枚举并优化设备指令处理逻辑
- 新增 AlarmTypeEnum 枚举类,定义 SOS 和静止报警类型
-优化 AppAuthController 中版本信息解析逻辑,增强空值处理
- 统一设备指令接口参数类型为 DeviceXinghanInstructDto
- 在 DeviceXinghanBizService 中实现 SOS 报警创建逻辑
-重构 XinghanDeviceDataRule 报警处理流程,使用统一枚举类型- 添加蓝牙模式下 SOS 指令的特殊处理逻辑- 完善报警 Redis 缓存键构建和续期机制
2025-09-25 08:31:32 +08:00
91f0241181 feat(mqtt): 添加电池电量百分比字段并适配控制列表显示
- 在 MqttXinghanJson 中新增 batteryPercentage 字段- 在 XinghanDeviceDataRule 中设置 batteryPercentage 值
- 适配控制百分比列表显示电池电量信息
2025-09-23 15:55:01 +08:00
baa341c2bf feat(equipment): 实现设备维修记录图片管理和报警处理功能
-重构文件哈希工具类路径并优化上传逻辑,支持秒传
- 新增维修记录图片ID列表字段及删除旧图片逻辑- 设备维修记录查询增加设备名称模糊搜索条件
-日期查询条件添加格式化注解支持 yyyy-MM-dd- MQTT规则中新增SOS与静止报警处理机制
- 实现报警生命周期管理(开始/结束)及Redis缓存控制
- 添加报警信息入库和位置解析功能
- 优化设备状态数据解析与经纬度异步存储逻辑
2025-09-23 14:21:05 +08:00
9c98fa9077 Merge branch 'dyf-device' into 6170 2025-09-23 13:58:09 +08:00
89f08c2d91 导入导出设备数据 2025-09-23 13:56:34 +08:00
135e6d6899 设备分享 2025-09-23 11:42:44 +08:00
49e9066033 feat(equipment): 实现设备维修记录图片管理和报警处理功能
-重构文件哈希工具类路径并优化上传逻辑,支持秒传
- 新增维修记录图片ID列表字段及删除旧图片逻辑- 设备维修记录查询增加设备名称模糊搜索条件
-日期查询条件添加格式化注解支持 yyyy-MM-dd- MQTT规则中新增SOS与静止报警处理机制
- 实现报警生命周期管理(开始/结束)及Redis缓存控制
- 添加报警信息入库和位置解析功能
- 优化设备状态数据解析与经纬度异步存储逻辑
2025-09-23 11:31:11 +08:00
e2821566c8 事件地址 2025-09-22 18:18:05 +08:00
5b3a92c80d 查询所有设备类型 2025-09-22 16:34:32 +08:00
9d642f4913 分页查询围栏进出记录列表 2025-09-22 15:58:00 +08:00
f07ec53645 控制中心设备绑定修改3 2025-09-22 14:05:39 +08:00
74b0059aca 控制中心设备绑定修改2 2025-09-22 14:03:44 +08:00
8e66f1ca28 控制中心设备绑定修改 2025-09-22 12:41:54 +08:00
1b33356a2a 在线状态修改 2025-09-22 11:02:26 +08:00
63 changed files with 1694 additions and 202 deletions

View File

@ -263,18 +263,21 @@ public class AppAuthController {
@GetMapping("/version") @GetMapping("/version")
public R<List<SysDictDataVo>> getAppVersion() { public R<List<SysDictDataVo>> getAppVersion() {
List<SysDictDataVo> list = dictTypeService.selectDictDataByType("app_version"); List<SysDictDataVo> list = dictTypeService.selectDictDataByType("app_version");
list.forEach(d -> { list.forEach(d -> {
String[] arr = d.getRemark().split("\\|"); // 1. 安全拆分
d.setDictLabel(d.getDictLabel()); // ios/android String[] arr = d.getRemark() == null ? new String[0] : d.getRemark().split("\\|");
d.setDictValue(arr[0]); // 版本号 if (arr.length < 2) { // 格式不对
d.setRemark(arr[1]); // 下载地址 log.warn("字典数据 app_version 格式非法dictLabel={}, remark={}", d.getDictLabel(), d.getRemark());
d.setDictValue(""); // 或者 d.setDictValue(d.getDictValue());
d.setRemark(""); // 下载地址留空
return; // 跳过
}
// 2. 正常赋值
d.setDictValue(arr[0].trim()); // 版本号
d.setRemark(arr[1].trim()); // 下载地址
}); });
// 只保留方法体:筛选 label=ios 且版本号 ≥ 2.5.0 的列表
// List<SysDictDataVo> result = list.stream()
// .peek(d -> { String[] a = d.getRemark().split("\\|"); d.setDictValue(a[0]); d.setRemark(a[1]); })
// .filter(d -> "ios".equalsIgnoreCase(d.getDictLabel()))
// .filter(d -> VersionComparator.INSTANCE.compare(d.getDictValue(), "2.5.0") >= 0)
// .toList();
return R.ok(list); return R.ok(list);
} }

View File

@ -10,6 +10,7 @@ import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation; import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
import com.fuyuanshen.web.service.device.DeviceBJQBizService; import com.fuyuanshen.web.service.device.DeviceBJQBizService;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService; import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -70,7 +71,7 @@ public class AppDeviceXinghanController extends BaseController {
*/ */
@Log(title = "xinghan指令-静电预警档位") @Log(title = "xinghan指令-静电预警档位")
@PostMapping("/DetectGradeSettings") @PostMapping("/DetectGradeSettings")
public R<Void> DetectGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> DetectGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
appDeviceService.upDetectGradeSettings(params); appDeviceService.upDetectGradeSettings(params);
return R.ok(); return R.ok();
@ -82,7 +83,7 @@ public class AppDeviceXinghanController extends BaseController {
*/ */
@Log(title = "xinghan指令-照明档位") @Log(title = "xinghan指令-照明档位")
@PostMapping("/LightGradeSettings") @PostMapping("/LightGradeSettings")
public R<Void> LightGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> LightGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
appDeviceService.upLightGradeSettings(params); appDeviceService.upLightGradeSettings(params);
return R.ok(); return R.ok();
@ -94,7 +95,7 @@ public class AppDeviceXinghanController extends BaseController {
*/ */
@Log(title = "xinghan指令-SOS档位s") @Log(title = "xinghan指令-SOS档位s")
@PostMapping("/SOSGradeSettings") @PostMapping("/SOSGradeSettings")
public R<Void> SOSGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> SOSGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
appDeviceService.upSOSGradeSettings(params); appDeviceService.upSOSGradeSettings(params);
return R.ok(); return R.ok();
@ -106,7 +107,7 @@ public class AppDeviceXinghanController extends BaseController {
*/ */
@Log(title = "xinghan指令-静止报警状态") @Log(title = "xinghan指令-静止报警状态")
@PostMapping("/ShakeBitSettings") @PostMapping("/ShakeBitSettings")
public R<Void> ShakeBitSettings(@RequestBody DeviceInstructDto params) { public R<Void> ShakeBitSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
appDeviceService.upShakeBitSettings(params); appDeviceService.upShakeBitSettings(params);
return R.ok(); return R.ok();

View File

@ -61,5 +61,14 @@ public class MqttXinghanJson {
*/ */
@JsonProperty("sta_latitude") @JsonProperty("sta_latitude")
public String stalatitude; public String stalatitude;
/**
* 第十二键值对系统现状0关机1仅充电2开机未充电,3开机且充电
*/
@JsonProperty("sta_system")
public String stasystem;
/**
* 电量百分比(适配控制列表显示)
*/
public String batteryPercentage;
} }

View File

@ -71,6 +71,8 @@ public class RedisKeyExpirationListener implements MessageListener {
deviceUpdateWrapper.eq("device_imei", element); deviceUpdateWrapper.eq("device_imei", element);
deviceUpdateWrapper.set("online_status", 0); deviceUpdateWrapper.set("online_status", 0);
deviceMapper.update(deviceUpdateWrapper); deviceMapper.update(deviceUpdateWrapper);
}else{
RedisUtils.deleteObject(deviceOnlineStatusRedisKey);
} }
} finally { } finally {
//释放锁 //释放锁

View File

@ -54,7 +54,7 @@ public class ReceiverMessageHandler implements MessageHandler {
RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24)); RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24));
//在线状态 //在线状态
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62)); RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(120));
} }
String state = payloadDict.getStr("state"); String state = payloadDict.getStr("state");

View File

@ -90,6 +90,8 @@ public class BjqAlarmRule implements MqttMessageRule {
deviceAlarmBo.setDurationTime(durationBetween); deviceAlarmBo.setDurationTime(durationBetween);
// 0已处理1未处理 // 0已处理1未处理
deviceAlarmBo.setTreatmentState(0); deviceAlarmBo.setTreatmentState(0);
// 告警状态0 解除告警, 1 告警中
deviceAlarmBo.setAlarmState(0);
deviceAlarmService.updateByBo(deviceAlarmBo); deviceAlarmService.updateByBo(deviceAlarmBo);
} }
} }
@ -107,6 +109,8 @@ public class BjqAlarmRule implements MqttMessageRule {
deviceAlarmBo.setStartTime(new Date()); deviceAlarmBo.setStartTime(new Date());
// 0已处理1未处理 // 0已处理1未处理
deviceAlarmBo.setTreatmentState(1); deviceAlarmBo.setTreatmentState(1);
// 告警状态0 解除告警, 1 告警中
deviceAlarmBo.setAlarmState(1);
// LoginUser loginUser = LoginHelper.getLoginUser(); // LoginUser loginUser = LoginHelper.getLoginUser();
// deviceAlarmBo.setCreateBy(loginUser.getUserId()); // deviceAlarmBo.setCreateBy(loginUser.getUserId());

View File

@ -5,6 +5,8 @@ import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.core.utils.StringUtils;
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.mapper.DeviceMapper;
import com.fuyuanshen.equipment.service.DeviceService;
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;
@ -38,6 +40,7 @@ import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVIC
public class BjqLocationDataRule implements MqttMessageRule { public class BjqLocationDataRule implements MqttMessageRule {
private final MqttGateway mqttGateway; private final MqttGateway mqttGateway;
private final DeviceService deviceService;
@Override @Override
@ -56,6 +59,8 @@ public class BjqLocationDataRule implements MqttMessageRule {
// 异步发送经纬度到Redis // 异步发送经纬度到Redis
asyncSendLocationToRedisWithFuture(context.getDeviceImei(), latitude, longitude); asyncSendLocationToRedisWithFuture(context.getDeviceImei(), latitude, longitude);
// 异步保存数据
asyncSaveLocationToMySQLWithFuture(context.getDeviceImei(), latitude, longitude);
Map<String, Object> map = buildLocationDataMap(latitude, longitude); Map<String, Object> map = buildLocationDataMap(latitude, longitude);
mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map)); mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map));
@ -114,13 +119,13 @@ public class BjqLocationDataRule 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.isBlank(latitude) || StringUtils.isBlank(longitude)) {
return; return;
} }
// String[] latArr = latitude.split("\\."); // String[] latArr = latitude.split("\\.");
// String[] lonArr = longitude.split("\\."); // String[] lonArr = longitude.split("\\.");
// // 将位置信息存储到Redis中 // // 将位置信息存储到Redis中
String redisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DEVICE_LOCATION_KEY_PREFIX; String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX;
// String redisObj = RedisUtils.getCacheObject(redisKey); // String redisObj = RedisUtils.getCacheObject(redisKey);
// JSONObject jsonOBj = JSONObject.parseObject(redisObj); // JSONObject jsonOBj = JSONObject.parseObject(redisObj);
// if(jsonOBj != null){ // if(jsonOBj != null){
@ -153,7 +158,6 @@ public class BjqLocationDataRule implements MqttMessageRule {
locationInfo.put("timestamp", System.currentTimeMillis()); locationInfo.put("timestamp", System.currentTimeMillis());
String locationJson = JsonUtils.toJsonString(locationInfo); String locationJson = JsonUtils.toJsonString(locationInfo);
// 存储到Redis // 存储到Redis
@ -171,12 +175,38 @@ public class BjqLocationDataRule implements MqttMessageRule {
}); });
} }
/**
* 异步保存位置信息到MySQL数据库
*
* @param deviceImei 设备IMEI
* @param latitude 纬度
* @param longitude 经度
*/
public void asyncSaveLocationToMySQLWithFuture(String deviceImei, String latitude, String longitude) {
CompletableFuture.runAsync(() -> {
try {
if (StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)) {
return;
}
// 调用服务层方法更新设备位置信息
deviceService.updateDeviceLocationByImei(deviceImei, longitude, latitude);
log.info("位置信息已异步保存到MySQL: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
} catch (Exception e) {
log.error("异步保存位置信息到MySQL时出错: device={}, error={}", deviceImei, e.getMessage(), e);
}
});
}
/** /**
* 存储设备30天历史轨迹到Redis (使用Sorted Set) * 存储设备30天历史轨迹到Redis (使用Sorted Set)
*/ */
public void storeDeviceTrajectoryWithSortedSet(String deviceImei, String locationJson) { public void storeDeviceTrajectoryWithSortedSet(String deviceImei, String locationJson) {
try { try {
String trajectoryKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX; String trajectoryKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_LOCATION_HISTORY_KEY_PREFIX;
// String trajectoryKey = "device:trajectory:zset:" + deviceImei; // String trajectoryKey = "device:trajectory:zset:" + deviceImei;
// String locationJson = JsonUtils.toJsonString(locationInfo); // String locationJson = JsonUtils.toJsonString(locationInfo);
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
@ -203,10 +233,10 @@ public class BjqLocationDataRule implements MqttMessageRule {
intData.add(11); intData.add(11);
intData.add(Integer.parseInt(latArr[0])); intData.add(Integer.parseInt(latArr[0]));
String str1 = latArr[1]; String str1 = latArr[1];
intData.add(Integer.parseInt(str1.substring(0,4))); intData.add(Integer.parseInt(str1.substring(0, 4)));
String str2 = lonArr[1]; String str2 = lonArr[1];
intData.add(Integer.parseInt(lonArr[0])); intData.add(Integer.parseInt(lonArr[0]));
intData.add(Integer.parseInt(str2.substring(0,4))); intData.add(Integer.parseInt(str2.substring(0, 4)));
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("instruct", intData); map.put("instruct", intData);

View File

@ -1,33 +1,42 @@
package com.fuyuanshen.global.mqtt.rule.xinghan; package com.fuyuanshen.global.mqtt.rule.xinghan;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
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;
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.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
import com.fuyuanshen.equipment.mapper.DeviceAlarmMapper;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
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;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext; import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.base.MqttXinghanJson;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants;
import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants; import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import com.fuyuanshen.web.enums.AlarmTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
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.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
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.global.mqtt.constants.DeviceRedisKeyConstants.*; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
@ -57,6 +66,9 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
private final IDeviceAlarmService deviceAlarmService;
private final DeviceService deviceService;
private final DeviceAlarmMapper deviceAlarmMapper;
@Override @Override
public void execute(MqttRuleContext context) { public void execute(MqttRuleContext context) {
@ -65,7 +77,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
// Latitude, longitude // Latitude, longitude
//主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间 //主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间
MqttXinghanJson deviceStatus = objectMapper.convertValue(context.getPayloadDict(), MqttXinghanJson.class); MqttXinghanJson deviceStatus = objectMapper.convertValue(context.getPayloadDict(), MqttXinghanJson.class);
deviceStatus.setBatteryPercentage(deviceStatus.getStaPowerPercent().toString());
// 发送设备状态和位置信息到Redis // 发送设备状态和位置信息到Redis
asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus); asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
@ -99,12 +111,113 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
// 异步发送经纬度到Redis // 异步发送经纬度到Redis
asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude()); asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude());
// 保存报警信息
saveAlarm(deviceImei,deviceStatus);
} catch (Exception e) { } catch (Exception e) {
log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e); log.error("异步发送设备信息到Redis时出错: device={}, error={}", deviceImei, e.getMessage(), e);
} }
}); });
} }
/**
* 入口保存报警SOS 与 Shake 完全独立)
*/
public void saveAlarm(String deviceImei, MqttXinghanJson status) {
int sos = Optional.ofNullable(status.getStaSOSGrade()).orElse(0);
// 1. 处理 SOS 报警
handleSingleAlarm(deviceImei,
sos > 0,
AlarmTypeEnum.SOS);
int shake = Optional.ofNullable(status.getStaShakeBit()).orElse(0);
// 2. 处理 Shake 报警
handleSingleAlarm(deviceImei,
shake > 0,
AlarmTypeEnum.SHAKE);
}
/**
* 通用:对单个报警源的“开始/结束”生命周期管理
*/
private void handleSingleAlarm(String deviceImei, boolean nowAlarming, AlarmTypeEnum type) {
String redisKey = buildAlarmRedisKey(deviceImei, type);
Long alarmId = RedisUtils.getCacheObject(redisKey);
// ---------- 情况 1当前正在报警 ----------
if (nowAlarming) {
// 已存在未结束报警 -> 什么都不做(同一条报警)
if (alarmId != null) {
// key 还在 -> 同一条报警,只续期
RedisUtils.setCacheObject(redisKey, alarmId, Duration.ofMinutes(10));
return;
}
// 不存在 -> 新建
DeviceAlarmBo bo = createAlarmBo(deviceImei, type);
deviceAlarmService.insertByBo(bo);
RedisUtils.setCacheObject(redisKey, bo.getId(), Duration.ofMinutes(10)); // 5分钟后结束过期
return;
}
// ---------- 情况 2当前不报警 ----------
if (alarmId != null) {
// 结束它
finishAlarm(alarmId);
RedisUtils.deleteObject(redisKey);
}
}
/**
* 结束报警:写库
*/
private void finishAlarm(Long alarmId) {
DeviceAlarmVo vo = deviceAlarmService.queryById(alarmId);
if (vo == null || vo.getTreatmentState() == 0) {
return; // 已处理或已被删
}
DeviceAlarmBo bo = BeanUtil.toBean(vo, DeviceAlarmBo.class);
bo.setFinishTime(new Date());
bo.setDurationTime(DurationUtils.getDurationBetween(vo.getStartTime(), bo.getFinishTime()));
bo.setTreatmentState(0); // 已处理
deviceAlarmService.updateByBo(bo);
}
/**
* 新建报警 Bo
*/
private DeviceAlarmBo createAlarmBo(String deviceImei, AlarmTypeEnum type) {
Device device = deviceService.selectDeviceByImei(deviceImei);
if (device == null) {
return null;
}
DeviceAlarmBo bo = new DeviceAlarmBo();
bo.setDeviceId(device.getId());
bo.setDeviceImei(deviceImei);
bo.setDeviceAction(2); // 自动报警
bo.setStartTime(new Date());
bo.setTreatmentState(1); // 未处理
bo.setTenantId(device.getTenantId());
// 报警内容
bo.setContent("自动报警:" + type.getDesc());
// 位置
String location = RedisUtils.getCacheObject(
GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX);
if (StrUtil.isNotBlank(location)) {
bo.setLocation(JSONObject.parseObject(location).getString("address"));
}
return bo;
}
/**
* key 构建
*/
private String buildAlarmRedisKey(String deviceImei, AlarmTypeEnum type) {
return StrUtil.format("{}{}{}{}:alarm_id",
GLOBAL_REDIS_KEY, DEVICE_KEY_PREFIX, deviceImei, type.getSuffix());
}
/** /**
* 异步发送位置信息到Redis使用CompletableFuture * 异步发送位置信息到Redis使用CompletableFuture
* *

View File

@ -98,21 +98,24 @@ public class MqttMessageConsumer {
String threadName = Thread.currentThread().getName(); String threadName = Thread.currentThread().getName();
try { try {
log.info("业务处理线程 {} 开始处理消息: {}", threadName, message); log.info("业务处理线程 {} 开始处理消息: {}", threadName, message);
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ; // String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey); // String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey);
if(StringUtils.isBlank(deviceOnlineStatusRedis)){ // if(StringUtils.isBlank(deviceOnlineStatusRedis)){
// UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
// updateWrapper.eq("device_imei", message)
// .set("online_status", 1);
// deviceMapper.update(updateWrapper);
// }
QueryWrapper<Device> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("device_imei", message);
queryWrapper.eq("online_status", 1);
Long count = deviceMapper.selectCount(queryWrapper);
if(count == 0){
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>(); UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("device_imei", message) updateWrapper.eq("device_imei", message)
.set("online_status", 1); .set("online_status", 1);
deviceMapper.update(updateWrapper); deviceMapper.update(updateWrapper);
} }
// QueryWrapper<Device> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("device_imei", message);
// queryWrapper.eq("online_status", 1);
// Long count = deviceMapper.selectCount(queryWrapper);
// if(count == 0){
//
// }
// 模拟业务处理耗时 // 模拟业务处理耗时
// Thread.sleep(200); // Thread.sleep(200);

View File

@ -0,0 +1,142 @@
package com.fuyuanshen.web.controller;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.equipment.domain.vo.*;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.equipment.service.IWeatherService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 大屏数据
*
* @author: 默苍璃
* @date: 2025-09-27 08:42
*/
@Tag(name = "大屏数据", description = "大屏数据")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/largeScreen")
public class LargeScreenController {
private final IDeviceAlarmService deviceAlarmService;
private final DeviceService deviceService;
private final IWeatherService weatherService;
/**
* 获取设备总览信息
* 包含设备总数、在线设备数量、设备型号数量
*/
@GetMapping("/getDeviceOverview")
public R<DeviceOverviewVo> getDeviceOverview() {
return R.ok(deviceService.getDeviceOverview());
}
/**
* 获取 实时报警信息
* RealtimeAlarm
*/
@GetMapping("/getRealtimeAlarm")
public R<List<DeviceAlarmVo>> getRealtimeAlarm() {
return R.ok(deviceAlarmService.getRealtimeAlarm());
}
/**
* 获取报警统计数据
* 包括正在报警数量、报警总数、已处理报警数量
*/
@GetMapping("/getAlarmStatistics")
public R<AlarmStatisticsVo> getAlarmStatistics() {
return R.ok(deviceAlarmService.getAlarmStatistics());
}
/**
* 获取最近一年每月告警统计数据
*/
@GetMapping("/getMonthlyAlarmStatistics")
public R<MonthlyAlarmStatisticsVo> getMonthlyAlarmStatistics() {
return R.ok(deviceAlarmService.getMonthlyAlarmStatistics());
}
/**
* 获取设备通讯方式统计数据
* 包含通讯方式名称、设备总数、异常设备数
*/
@GetMapping("/getDeviceCommunicationModeStatistics")
public R<List<DeviceCommunicationModeStatisticsVo>> getDeviceCommunicationModeStatistics() {
return R.ok(deviceService.getDeviceCommunicationModeStatistics());
}
/**
* 获取设备使用频次统计数据
*
* @param days 天数近一个月传30近半年传180
*/
@GetMapping("/getDeviceUsageFrequency")
public R<List<DeviceUsageFrequencyVo>> getDeviceUsageFrequency(@RequestParam int days) {
return R.ok(deviceService.getDeviceUsageFrequency(days));
}
/**
* 根据条件查询设备位置信息
*
* @param groupId 设备分组ID
* @param deviceType 设备类型
* @param deviceImei 设备IMEI
* @return 设备位置信息列表
*/
@GetMapping("/getDeviceLocationInfo")
public R<List<DeviceLocationVo>> getDeviceLocationInfo(
@RequestParam(required = false) Long groupId,
@RequestParam(required = false) Long deviceType,
@RequestParam(required = false) String deviceImei) {
return R.ok(deviceService.getDeviceLocationInfo(groupId, deviceType, deviceImei));
}
/**
* 根据经纬度获取天气信息
*
* @param latitude 纬度
* @param longitude 经度
* @return 天气信息
*/
@GetMapping("/weather")
public R<WeatherInfoVo> getWeatherInfo(
@RequestParam Double latitude,
@RequestParam Double longitude) {
if (latitude == null || longitude == null) {
return R.fail("经纬度参数不能为空");
}
// 简单的经纬度范围校验
if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) {
return R.fail("经纬度参数范围不正确");
}
WeatherInfoVo weatherInfo = weatherService.getWeatherByCoordinates(latitude, longitude);
if (weatherInfo != null) {
return R.ok(weatherInfo);
} else {
return R.fail("获取天气信息失败");
}
}
}

View File

@ -10,6 +10,7 @@ import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation; import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
import com.fuyuanshen.common.web.core.BaseController; import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo; import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService; import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -80,7 +81,7 @@ public class DeviceXinghanController extends BaseController {
* 3,2,1,0,分别表示高档/中档/低挡/关闭 * 3,2,1,0,分别表示高档/中档/低挡/关闭
*/ */
@PostMapping("/DetectGradeSettings") @PostMapping("/DetectGradeSettings")
public R<Void> DetectGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> DetectGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
deviceXinghanBizService.upDetectGradeSettings(params); deviceXinghanBizService.upDetectGradeSettings(params);
return R.ok(); return R.ok();
@ -91,7 +92,7 @@ public class DeviceXinghanController extends BaseController {
* 照明档位2,1,0,分别表示弱光/强光/关闭 * 照明档位2,1,0,分别表示弱光/强光/关闭
*/ */
@PostMapping("/LightGradeSettings") @PostMapping("/LightGradeSettings")
public R<Void> LightGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> LightGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
deviceXinghanBizService.upLightGradeSettings(params); deviceXinghanBizService.upLightGradeSettings(params);
return R.ok(); return R.ok();
@ -102,7 +103,7 @@ public class DeviceXinghanController extends BaseController {
* SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭 * SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/ */
@PostMapping("/SOSGradeSettings") @PostMapping("/SOSGradeSettings")
public R<Void> SOSGradeSettings(@RequestBody DeviceInstructDto params) { public R<Void> SOSGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
deviceXinghanBizService.upSOSGradeSettings(params); deviceXinghanBizService.upSOSGradeSettings(params);
return R.ok(); return R.ok();
@ -113,7 +114,7 @@ public class DeviceXinghanController extends BaseController {
* 静止报警状态0-未静止报警1-正在静止报警。 * 静止报警状态0-未静止报警1-正在静止报警。
*/ */
@PostMapping("/ShakeBitSettings") @PostMapping("/ShakeBitSettings")
public R<Void> ShakeBitSettings(@RequestBody DeviceInstructDto params) { public R<Void> ShakeBitSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject // params 转 JSONObject
deviceXinghanBizService.upShakeBitSettings(params); deviceXinghanBizService.upShakeBitSettings(params);
return R.ok(); return R.ok();

View File

@ -0,0 +1,15 @@
package com.fuyuanshen.web.domain.Dto;
import lombok.Data;
@Data
public class DeviceXinghanInstructDto {
private Long deviceId;
private String deviceImei;
/**
* 下发指令
*/
private String instructValue;
private Boolean isBluetooth = false;
}

View File

@ -0,0 +1,17 @@
package com.fuyuanshen.web.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 报警类型枚举
*/
@AllArgsConstructor
@Getter
public enum AlarmTypeEnum {
SOS("_sos", "SOS报警"),
SHAKE("_shake", "静止报警");
private final String suffix;
private final String desc;
}

View File

@ -10,18 +10,15 @@ import com.fuyuanshen.app.service.IAppBusinessFileService;
import com.fuyuanshen.app.service.IAppOperationVideoService; import com.fuyuanshen.app.service.IAppOperationVideoService;
import com.fuyuanshen.common.core.exception.ServiceException; import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.service.DeviceService; import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.system.service.ISysOssService;
import com.fuyuanshen.web.domain.vo.DeviceInfoVo; import com.fuyuanshen.web.domain.vo.DeviceInfoVo;
import com.fuyuanshen.web.util.FileHashUtil; import com.fuyuanshen.equipment.utils.FileHashUtil;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;

View File

@ -1,6 +1,7 @@
package com.fuyuanshen.web.service.device; package com.fuyuanshen.web.service.device;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@ -28,17 +29,21 @@ import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper; import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceType; import com.fuyuanshen.equipment.domain.DeviceType;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.equipment.enums.LightModeEnum; import com.fuyuanshen.equipment.enums.LightModeEnum;
import com.fuyuanshen.equipment.mapper.DeviceLogMapper; import com.fuyuanshen.equipment.mapper.DeviceLogMapper;
import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.base.MqttXinghanJson;
import com.fuyuanshen.global.mqtt.config.MqttGateway; import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.constants.MqttConstants; import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto; import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo; import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo;
import com.fuyuanshen.web.enums.AlarmTypeEnum;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -68,6 +73,7 @@ public class DeviceXinghanBizService {
private final MqttGateway mqttGateway; private final MqttGateway mqttGateway;
private final DeviceLogMapper deviceLogMapper; private final DeviceLogMapper deviceLogMapper;
private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper; private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper;
private final IDeviceAlarmService deviceAlarmService;
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@ -95,28 +101,39 @@ public class DeviceXinghanBizService {
/** /**
* 设置静电预警档位 * 设置静电预警档位
*/ */
public void upDetectGradeSettings(DeviceInstructDto dto) { public void upDetectGradeSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_DetectGrade","静电预警档位"); sendCommand(dto, "ins_DetectGrade","静电预警档位");
} }
/** /**
* 设置照明档位 * 设置照明档位
*/ */
public void upLightGradeSettings(DeviceInstructDto dto) { public void upLightGradeSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_LightGrade","照明档位"); sendCommand(dto, "ins_LightGrade","照明档位");
} }
/** /**
* 设置SOS档位 * 设置SOS档位
*/ */
public void upSOSGradeSettings(DeviceInstructDto dto) { public void upSOSGradeSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_SOSGrade","SOS档位"); if(dto.getIsBluetooth()){
long deviceId = dto.getDeviceId();
// 1. 使用Optional简化空值检查使代码更简洁
Device device = Optional.ofNullable(deviceMapper.selectById(deviceId))
.orElseThrow(() -> new ServiceException("设备不存在"));
int sosGrade = Integer.parseInt(dto.getInstructValue());
// 6. 新建报警信息
createAlarm(device.getId(),device.getDeviceImei(),"ins_SOSGrade",sosGrade);
}else {
sendCommand(dto, "ins_SOSGrade","SOS档位");
}
} }
/** /**
* 设置强制报警 * 设置强制报警
*/ */
public void upShakeBitSettings(DeviceInstructDto dto) { public void upShakeBitSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_ShakeBit","强制报警"); sendCommand(dto, "ins_ShakeBit","强制报警");
} }
@ -465,7 +482,7 @@ public class DeviceXinghanBizService {
* @param payloadKey 指令负载数据的键名 * @param payloadKey 指令负载数据的键名
* @param deviceAction 设备操作类型描述 * @param deviceAction 设备操作类型描述
*/ */
private void sendCommand(DeviceInstructDto dto, String payloadKey, String deviceAction) { private void sendCommand(DeviceXinghanInstructDto dto, String payloadKey, String deviceAction) {
long deviceId = dto.getDeviceId(); long deviceId = dto.getDeviceId();
// 1. 使用Optional简化空值检查使代码更简洁 // 1. 使用Optional简化空值检查使代码更简洁
@ -509,6 +526,9 @@ public class DeviceXinghanBizService {
deviceAction, deviceAction,
content, content,
AppLoginHelper.getUserId()); AppLoginHelper.getUserId());
// 6. 新建报警信息
createAlarm(device.getId(),deviceImei,payloadKey,value);
} }
// private boolean isDeviceOffline(String imei) { // private boolean isDeviceOffline(String imei) {
@ -516,6 +536,48 @@ public class DeviceXinghanBizService {
// return getDeviceStatus(imei); // return getDeviceStatus(imei);
// } // }
/**
* 新建报警 Bo
*/
private void createAlarm(Long deviceId, String deviceImei,
String payloadKey, int value) {
// 这里直接放你原来的 createAlarmBo 全部逻辑
if (!"ins_SOSGrade".equals(payloadKey) || value == 0) {
return;
}
AlarmTypeEnum type = value == 1 ? AlarmTypeEnum.SOS : AlarmTypeEnum.SHAKE;
String redisKey = buildAlarmRedisKey(deviceImei, type);
Long alarmId = RedisUtils.getCacheObject(redisKey);
// 已存在未结束报警 -> 什么都不做(同一条报警)
if (alarmId != null) {
// key 还在 -> 同一条报警,只续期
RedisUtils.setCacheObject(redisKey, alarmId, Duration.ofMinutes(10));
return;
}
// 不存在 -> 新建
DeviceAlarmBo bo = new DeviceAlarmBo();
bo.setDeviceId(deviceId);
bo.setDeviceImei(deviceImei);
bo.setDeviceAction(0); // 强制报警
bo.setStartTime(new Date());
bo.setTreatmentState(1); // 未处理
bo.setContent("强制报警:" + type.getDesc());
String location = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX);
if (StrUtil.isNotBlank(location)) {
bo.setLocation(JSONObject.parseObject(location).getString("address"));
}
deviceAlarmService.insertByBo(bo);
RedisUtils.setCacheObject(redisKey, bo.getId(), Duration.ofMinutes(10));
}
/**
* key 构建
*/
private String buildAlarmRedisKey(String deviceImei, AlarmTypeEnum type) {
return StrUtil.format("{}{}{}{}:alarm_id",
GLOBAL_REDIS_KEY, DEVICE_KEY_PREFIX, deviceImei, type.getSuffix());
}
/** /**
* 记录设备操作日志 * 记录设备操作日志
* @param deviceId 设备ID * @param deviceId 设备ID

View File

@ -49,7 +49,7 @@ public class APPDeviceType extends TenantEntity {
@Schema(name = "联网方式", example = "0:无;1:4G;2:WIFI") @Schema(name = "联网方式", example = "0:无;1:4G;2:WIFI")
private String networkWay; private String networkWay;
@Schema(name = "通讯方式", example = "0:4G;1:蓝牙") @Schema(name = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙")
private String communicationMode; private String communicationMode;

View File

@ -50,6 +50,9 @@ public class AppDeviceBindRecord extends TenantEntity {
*/ */
private Date bindingTime; private Date bindingTime;
/**
* 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/
private Integer communicationMode; private Integer communicationMode;
} }

View File

@ -59,7 +59,7 @@ public class APPDeviceQueryCriteria {
@Schema(name = "租户ID") @Schema(name = "租户ID")
private Long tenantId; private Long tenantId;
@Schema(name = "通讯方式", example = "0:4G;1:蓝牙") @Schema(name = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙")
private Integer communicationMode; private Integer communicationMode;
} }

View File

@ -10,6 +10,9 @@ public class APPDeviceTypeVo {
private String typeName; private String typeName;
/**
* 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/
private String communicationMode; private String communicationMode;
} }

View File

@ -33,7 +33,7 @@ public class AppDeviceDetailVo {
private String deviceMac; private String deviceMac;
/** /**
* 通讯方式 0:4G;1:蓝牙 * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/ */
private Integer communicationMode; private Integer communicationMode;

View File

@ -68,7 +68,7 @@ public class AppDeviceShareDetailVo implements Serializable {
private String deviceMac; private String deviceMac;
/** /**
* 通讯方式 0:4G;1:蓝牙 * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/ */
private Integer communicationMode; private Integer communicationMode;

View File

@ -114,4 +114,9 @@ public class AppDeviceShareVo implements Serializable {
* 创建时间 * 创建时间
*/ */
private String createTime; private String createTime;
/**
* 设备详情页面
*/
private String detailPageUrl;
} }

View File

@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
@ -33,23 +34,62 @@ public class IgnoreFailedImageConverter implements Converter<URL> {
@Override @Override
public WriteCellData<?> convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { public WriteCellData<?> convertToExcelData(URL value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (value == null) { if (value == null) {
return null; logger.debug("图片URL为空");
return new WriteCellData<>(new byte[0]);
} }
try { try {
logger.debug("开始加载图片: {}", value);
URLConnection conn = value.openConnection(); URLConnection conn = value.openConnection();
conn.setConnectTimeout(2000); // 2秒超时 // 增加连接和读取超时时间
conn.setReadTimeout(3000); // 3秒超时 conn.setConnectTimeout(10000); // 10秒连接超时
conn.setReadTimeout(30000); // 30秒读取超时
// 添加User-Agent避免被服务器拦截
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ExcelExporter/1.0");
// 如果是HTTP连接设置一些额外的属性
if (conn instanceof HttpURLConnection) {
HttpURLConnection httpConn = (HttpURLConnection) conn;
httpConn.setRequestMethod("GET");
// 不使用缓存
httpConn.setUseCaches(false);
// 跟随重定向
httpConn.setInstanceFollowRedirects(true);
}
long contentLength = conn.getContentLengthLong();
logger.debug("连接建立成功,图片大小: {} 字节", contentLength);
// 检查内容长度是否有效
if (contentLength == 0) {
logger.warn("图片文件为空: {}", value);
return new WriteCellData<>(new byte[0]);
}
// 限制图片大小(防止过大文件导致内存问题)
if (contentLength > 10 * 1024 * 1024) { // 10MB限制
logger.warn("图片文件过大 ({} bytes),跳过加载: {}", contentLength, value);
return new WriteCellData<>(new byte[0]);
}
try (InputStream inputStream = conn.getInputStream()) { try (InputStream inputStream = conn.getInputStream()) {
// byte[] bytes = FileUtils.readInputStream(inputStream, value.toString()); // byte[] bytes = FileUtils.readInputStream(inputStream, value.toString());
// 替代 FileUtils.readInputStream 的自定义方法 // 替代 FileUtils.readInputStream 的自定义方法
byte[] bytes = readInputStream(inputStream); byte[] bytes = readInputStream(inputStream);
// 检查读取到的数据是否为空
if (bytes == null || bytes.length == 0) {
logger.warn("读取到空的图片数据: {}", value);
return new WriteCellData<>(new byte[0]);
}
logger.debug("成功读取图片数据,大小: {} 字节", bytes.length);
return new WriteCellData<>(bytes); return new WriteCellData<>(bytes);
} }
} catch (Exception e) { } catch (Exception e) {
// 静默忽略错误,只记录日志 // 静默忽略错误,只记录日志
logger.debug("忽略图片加载失败: {}, 原因: {}", value, e.getMessage()); logger.warn("图片加载失败: {}, 原因: {}", value, e.getMessage(), e);
// return null; // 返回null表示不写入图片 // return null; // 返回null表示不写入图片
return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null return new WriteCellData<>(new byte[0]); // 返回空数组而不是 null
} }
@ -66,9 +106,17 @@ public class IgnoreFailedImageConverter implements Converter<URL> {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int bytesRead; int bytesRead;
int totalBytes = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) { while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead); outputStream.write(buffer, 0, bytesRead);
totalBytes += bytesRead;
// 如果读取的数据过大,提前终止
if (totalBytes > 10 * 1024 * 1024) { // 10MB限制
logger.warn("读取的图片数据超过10MB限制提前终止");
break;
}
} }
return outputStream.toByteArray(); return outputStream.toByteArray();

View File

@ -69,4 +69,9 @@ public class DeviceFenceAccessRecord extends BaseEntity {
*/ */
private Date eventTime; private Date eventTime;
/**
* 事件地址
*/
private String eventAddress;
} }

View File

@ -114,5 +114,9 @@ public class DeviceAlarmBo extends TenantEntity {
*/ */
private Integer treatmentState; private Integer treatmentState;
/**
* 告警状态0 解除告警, 1 告警中
*/
private Integer alarmState;
} }

View File

@ -1,5 +1,6 @@
package com.fuyuanshen.equipment.domain.bo; package com.fuyuanshen.equipment.domain.bo;
import cn.idev.excel.annotation.ExcelProperty;
import com.fuyuanshen.common.core.validate.AddGroup; import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup; import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
@ -8,7 +9,9 @@ import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.util.Date; import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
/** /**
@ -25,19 +28,24 @@ public class DeviceFenceAccessRecordBo extends BaseEntity {
/** /**
* 记录ID * 记录ID
*/ */
@NotNull(message = "记录ID不能为空", groups = { EditGroup.class }) @NotNull(message = "记录ID不能为空", groups = {EditGroup.class})
private Long id; private Long id;
/** /**
* 围栏ID * 围栏ID
*/ */
@NotNull(message = "围栏ID不能为空", groups = { AddGroup.class, EditGroup.class }) @NotNull(message = "围栏ID不能为空", groups = {AddGroup.class, EditGroup.class})
private Long fenceId; private Long fenceId;
/**
* 围栏名称
*/
private String fenceName;
/** /**
* 设备标识 * 设备标识
*/ */
@NotBlank(message = "设备标识不能为空", groups = { AddGroup.class, EditGroup.class }) @NotBlank(message = "设备标识不能为空", groups = {AddGroup.class, EditGroup.class})
private String deviceId; private String deviceId;
/** /**
@ -48,19 +56,19 @@ public class DeviceFenceAccessRecordBo extends BaseEntity {
/** /**
* 事件类型 * 事件类型
*/ */
@NotNull(message = "事件类型不能为空", groups = { AddGroup.class, EditGroup.class }) @NotNull(message = "事件类型不能为空", groups = {AddGroup.class, EditGroup.class})
private Long eventType; private Long eventType;
/** /**
* 纬度 * 纬度
*/ */
@NotNull(message = "纬度不能为空", groups = { AddGroup.class, EditGroup.class }) @NotNull(message = "纬度不能为空", groups = {AddGroup.class, EditGroup.class})
private Double latitude; private Double latitude;
/** /**
* 经度 * 经度
*/ */
@NotNull(message = "经度不能为空", groups = { AddGroup.class, EditGroup.class }) @NotNull(message = "经度不能为空", groups = {AddGroup.class, EditGroup.class})
private Double longitude; private Double longitude;
/** /**
@ -71,9 +79,14 @@ public class DeviceFenceAccessRecordBo extends BaseEntity {
/** /**
* 事件时间 * 事件时间
*/ */
@NotNull(message = "事件时间不能为空", groups = { AddGroup.class, EditGroup.class }) @NotNull(message = "事件时间不能为空", groups = {AddGroup.class, EditGroup.class})
private Date eventTime; private Date eventTime;
/**
* 事件地址
*/
private String eventAddress;
/** /**
* 开始时间 * 开始时间
*/ */

View File

@ -11,6 +11,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.util.Date; import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -70,4 +72,6 @@ public class DeviceRepairRecordsBo extends BaseEntity {
@Schema(title = "维修后图片") @Schema(title = "维修后图片")
@JsonIgnore @JsonIgnore
private MultipartFile afterFile; private MultipartFile afterFile;
private List<Long> imageIds;
} }

View File

@ -19,24 +19,25 @@ import java.net.URL;
@ContentRowHeight(100) // 内容行高 @ContentRowHeight(100) // 内容行高
public class DeviceExcelExportDTO { public class DeviceExcelExportDTO {
@ExcelProperty("ID") // @ExcelProperty("ID")
private Long id; // private Long id;
@ExcelProperty("设备类型")
private Long deviceType; // @ExcelProperty("设备类型")
// private Long deviceType;
// @ExcelProperty("客户号") // @ExcelProperty("客户号")
// private Long customerId; // private Long customerId;
@ExcelProperty("所属客户") // @ExcelProperty("所属客户")
private String customerName; // private String customerName;
@ExcelProperty("设备名称") @ExcelProperty("设备名称")
@ColumnWidth(20) @ColumnWidth(20)
private String deviceName; private String deviceName;
@ExcelProperty(value = "设备图片", converter = IgnoreFailedImageConverter.class) @ExcelProperty(value = "设备图片", converter = IgnoreFailedImageConverter.class)
@ColumnWidth(15) // 设置图片列宽度 @ColumnWidth(30) // 设置图片列宽度
private URL devicePic; // 使用URL类型 private URL devicePic; // 使用URL类型
@ExcelProperty("设备MAC") @ExcelProperty("设备MAC")
@ -51,28 +52,37 @@ public class DeviceExcelExportDTO {
@ColumnWidth(20) @ColumnWidth(20)
private String deviceImei; private String deviceImei;
@ExcelProperty("经度") @ExcelProperty("设备类型")
private String longitude; @ColumnWidth(20)
private String typeName;
@ExcelProperty("纬度") // @ExcelProperty("经度")
private String latitude; // private String longitude;
// @ExcelProperty("纬度")
// private String latitude;
/**
* 绑定状态
* 0 未绑定
* 1 已绑定
*/
@ExcelProperty("绑定状态")
@ColumnWidth(20)
private String bindingStatus;
@ExcelProperty("备注") @ExcelProperty("备注")
@ColumnWidth(30) @ColumnWidth(30)
private String remark; private String remark;
@ExcelProperty("设备类型名称")
@ColumnWidth(20)
private String typeName;
/** /**
* 设备状态 * 设备状态
* 0 失效 * 0 失效
* 1 正常 * 1 正常
*/ */
@ExcelProperty("设备状态") // @ExcelProperty("设备状态")
@ColumnWidth(20) // @ColumnWidth(20)
private String deviceStatus; // private String deviceStatus;
@ExcelProperty("创建时间") @ExcelProperty("创建时间")
@ColumnWidth(20) @ColumnWidth(20)

View File

@ -16,24 +16,24 @@ import lombok.Data;
@ContentRowHeight(100) // 内容行高 @ContentRowHeight(100) // 内容行高
public class DeviceExcelImportDTO { public class DeviceExcelImportDTO {
@ExcelProperty("设备类型") // @ExcelProperty("设备类型")
private Long deviceType; // private Long deviceType;
@ExcelProperty("客户号")
private Long customerId;
@ExcelProperty("设备名称") @ExcelProperty("设备名称")
@ColumnWidth(20) @ColumnWidth(20)
private String deviceName; private String deviceName;
@ExcelProperty(value = "设备图片", converter = ByteArrayImageConverter.class) @ExcelProperty("设备类型名称")
@ColumnWidth(15) private String typeName;
private byte[] devicePic;
// 添加图片写入方法 // @ExcelProperty(value = "设备图片", converter = ByteArrayImageConverter.class)
public void setDevicePicFromBytes(byte[] bytes) { // @ColumnWidth(15)
this.devicePic = bytes; // private byte[] devicePic;
} //
// // 添加图片写入方法
// public void setDevicePicFromBytes(byte[] bytes) {
// this.devicePic = bytes;
// }
@ExcelProperty("设备MAC") @ExcelProperty("设备MAC")
@ColumnWidth(20) @ColumnWidth(20)
@ -43,24 +43,21 @@ public class DeviceExcelImportDTO {
@ColumnWidth(20) @ColumnWidth(20)
private String deviceImei; private String deviceImei;
@ExcelProperty("设备SN") @ExcelProperty("蓝牙名称")
@ColumnWidth(20) private String bluetoothName;
private String deviceSn;
@ExcelProperty("经度") // @ExcelProperty("设备SN")
private String longitude; // @ColumnWidth(20)
// private String deviceSn;
@ExcelProperty("纬度") //
private String latitude; // @ExcelProperty("经度")
// private String longitude;
//
// @ExcelProperty("纬度")
// private String latitude;
@ExcelProperty("备注") @ExcelProperty("备注")
@ColumnWidth(30) @ColumnWidth(30)
private String remark; private String remark;
@ExcelProperty("设备类型名称")
private String typeName;
@ExcelProperty("蓝牙名称")
private String bluetoothName;
} }

View File

@ -43,7 +43,6 @@ public class DeviceForm {
@Schema(title = "蓝牙名称") @Schema(title = "蓝牙名称")
private String bluetoothName; private String bluetoothName;
@Schema(title = "设备IMEI") @Schema(title = "设备IMEI")
private String deviceImei; private String deviceImei;

View File

@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.Date; import java.util.Date;
@ -51,8 +52,10 @@ public class DeviceRepairRecordsQueryCriteria extends BaseEntity {
private String repairPerson; private String repairPerson;
@Schema(title = "维修开始时间") @Schema(title = "维修开始时间")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date repairBeginTime; private Date repairBeginTime;
@Schema(title = "维修结束时间") @Schema(title = "维修结束时间")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date repairEndTime; private Date repairEndTime;
@Schema(title = "所属客户") @Schema(title = "所属客户")

View File

@ -0,0 +1,57 @@
package com.fuyuanshen.equipment.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 报警统计信息
*
* @author: fuyuanshen
* @date: 2025-09-27
*/
@Data
public class AlarmStatisticsVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 正在报警数量(未处理的报警)
*/
private Integer activeAlarms = 0;
/**
* 报警总数
*/
private Integer totalAlarms = 0;
/**
* 已处理报警数量
*/
private Integer processedAlarms = 0;
/**
* 强制报警数量
* device_action = 0
*/
private Integer forcedAlarms = 0;
/**
* 撞击闯入报警数量
* device_action = 1
*/
private Integer intrusionImpactAlarms = 0;
/**
* 手动报警数量
* device_action = 2
*/
private Integer manualAlarms = 0;
/**
* 电子围栏告警数量
* device_action = 3
*/
private Integer geoFenceAlarms = 0;
}

View File

@ -90,13 +90,13 @@ public class DeviceAlarmVo implements Serializable {
* 经度 * 经度
*/ */
@ExcelProperty(value = "经度") @ExcelProperty(value = "经度")
private Long longitude; private Double longitude;
/** /**
* 纬度 * 纬度
*/ */
@ExcelProperty(value = "纬度") @ExcelProperty(value = "纬度")
private Long latitude; private Double latitude;
/** /**
* 报警位置 * 报警位置
@ -123,6 +123,7 @@ public class DeviceAlarmVo implements Serializable {
private String durationTime; private String durationTime;
/** /**
* 处理状态
* 0已处理1未处理 * 0已处理1未处理
*/ */
@ExcelProperty(value = "0已处理1未处理") @ExcelProperty(value = "0已处理1未处理")
@ -135,5 +136,4 @@ public class DeviceAlarmVo implements Serializable {
@Schema(name = "设备图片") @Schema(name = "设备图片")
private String devicePic; private String devicePic;
} }

View File

@ -0,0 +1,39 @@
package com.fuyuanshen.equipment.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 设备通讯方式统计Vo
*
* @author: 默苍璃
* @date: 2025-09-27
*/
@Data
public class DeviceCommunicationModeStatisticsVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 通讯方式名称
* 0:4G;1:蓝牙;2:4G&蓝牙
*/
private String communicationModeName;
/**
* 通讯方式值
* 0:4G;1:蓝牙;2:4G&蓝牙
*/
private Integer communicationModeValue;
/**
* 设备总数
*/
private Integer totalDevices = 0;
/**
* 异常设备数
*/
private Integer abnormalDevices = 0;
}

View File

@ -77,13 +77,13 @@ public class DeviceFenceAccessRecordVo implements Serializable {
* 纬度 * 纬度
*/ */
@ExcelProperty(value = "纬度") @ExcelProperty(value = "纬度")
private Long latitude; private Double latitude;
/** /**
* 经度 * 经度
*/ */
@ExcelProperty(value = "经度") @ExcelProperty(value = "经度")
private Long longitude; private Double longitude;
/** /**
* 定位精度 * 定位精度
@ -97,6 +97,12 @@ public class DeviceFenceAccessRecordVo implements Serializable {
@ExcelProperty(value = "事件时间") @ExcelProperty(value = "事件时间")
private Date eventTime; private Date eventTime;
/**
* 事件地址
*/
@ExcelProperty(value = "事件地址")
private String eventAddress;
/** /**
* 记录创建时间 * 记录创建时间
*/ */

View File

@ -0,0 +1,36 @@
package com.fuyuanshen.equipment.domain.vo;
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 设备位置信息VO
*
* @author: 默苍璃
* @date: 2025-09-2714:33
*/
@Data
@Schema(description = "设备位置信息VO")
public class DeviceLocationVo {
@Schema(description = "设备ID")
private Long deviceId;
@Schema(description = "设备名称")
private String deviceName;
@Schema(description = "经度")
private String longitude;
@Schema(description = "纬度")
private String latitude;
@Schema(description = "设备是否在电子围栏内")
private Boolean inFence;
@Schema(description = "进入的电子围栏信息")
private DeviceGeoFence fenceInfo;
}

View File

@ -0,0 +1,32 @@
package com.fuyuanshen.equipment.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 设备总览信息
*
* @author: fuyuanshen
* @date: 2025-09-27
*/
@Data
public class DeviceOverviewVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 设备总数
*/
private Integer totalDevices = 0;
/**
* 在线设备数量
*/
private Integer onlineDevices = 0;
/**
* 设备型号数量
*/
private Integer deviceTypes = 0;
}

View File

@ -0,0 +1,27 @@
package com.fuyuanshen.equipment.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 设备使用频次统计Vo
*
* @author: 默苍璃
* @date: 2025-09-27
*/
@Data
public class DeviceUsageFrequencyVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 设备名称
*/
private String deviceName;
/**
* 使用频次
*/
private Integer frequency = 0;
}

View File

@ -0,0 +1,26 @@
package com.fuyuanshen.equipment.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* 每月告警统计信息
*
* @author: fuyuanshen
* @date: 2025-09-27
*/
@Data
public class MonthlyAlarmStatisticsVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 每月告警统计数据
* key: 月份 (m1-m12)
* value: 告警数量
*/
private Map<String, Integer> monthlyStatistics = new HashMap<>();
}

View File

@ -0,0 +1,98 @@
package com.fuyuanshen.equipment.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**天气信息视图对象
* @author: 默苍璃
* @date: 2025-09-2715:25
*/
@Data
@Schema(description = "天气信息视图对象")
public class WeatherInfoVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 经度
*/
@Schema(description = "经度")
private Double longitude;
/**
* 纬度
*/
@Schema(description = "纬度")
private Double latitude;
/**
* 天气状况
*/
@Schema(description = "天气状况")
private String weatherCondition;
/**
* 天气描述
*/
@Schema(description = "天气描述")
private String description;
/**
* 温度
*/
@Schema(description = "温度(摄氏度)")
private Double temperature;
/**
* 体感温度
*/
@Schema(description = "体感温度(摄氏度)")
private Double feelsLike;
/**
* 湿度
*/
@Schema(description = "湿度(%)")
private Integer humidity;
/**
* 气压
*/
@Schema(description = "气压(hPa)")
private Double pressure;
/**
* 能见度
*/
@Schema(description = "能见度(米)")
private Integer visibility;
/**
* 风速
*/
@Schema(description = "风速(m/s)")
private Double windSpeed;
/**
* 风向
*/
@Schema(description = "风向(度)")
private Integer windDirection;
/**
* 云量
*/
@Schema(description = "云量(%)")
private Integer cloudiness;
/**
* 更新时间
*/
@Schema(description = "数据更新时间")
private LocalDateTime updateTime;
}

View File

@ -72,7 +72,7 @@ public class UploadDeviceDataListener implements ReadListener<DeviceExcelImportD
// 设置图片数据 // 设置图片数据
byte[] imageData = rowImageMap.get(rowIndex); byte[] imageData = rowImageMap.get(rowIndex);
if (imageData != null) { if (imageData != null) {
recordWithImage.setDevicePicFromBytes(imageData); // recordWithImage.setDevicePicFromBytes(imageData);
} }
failedRecordsWithImages.add(recordWithImage); failedRecordsWithImages.add(recordWithImage);
@ -125,7 +125,6 @@ public class UploadDeviceDataListener implements ReadListener<DeviceExcelImportD
rowDeviceMap.put(rowIndex, device); rowDeviceMap.put(rowIndex, device);
rowDtoMap.put(rowIndex, data); rowDtoMap.put(rowIndex, data);
rowIndexList.add(rowIndex); rowIndexList.add(rowIndex);
} }
@ -191,7 +190,8 @@ public class UploadDeviceDataListener implements ReadListener<DeviceExcelImportD
if (device != null) { if (device != null) {
try { try {
byte[] imageData = picture.getPictureData().getData(); byte[] imageData = picture.getPictureData().getData();
String extraValue = getCellValue(sheet, rowIndex, 4); // 表示Excel表格中的第3列因为索引从0开始计算
String extraValue = getCellValue(sheet, rowIndex, 2);
String imageUrl = uploadAndGenerateUrl(imageData, extraValue); String imageUrl = uploadAndGenerateUrl(imageData, extraValue);
device.setDevicePic(imageUrl); device.setDevicePic(imageUrl);

View File

@ -4,10 +4,16 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.equipment.domain.DeviceAlarm; import com.fuyuanshen.equipment.domain.DeviceAlarm;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.vo.AlarmStatisticsVo;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
import com.fuyuanshen.equipment.domain.vo.MonthlyAlarmStatisticsVo;
import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/** /**
* 设备告警Mapper接口 * 设备告警Mapper接口
* *
@ -23,7 +29,7 @@ public interface DeviceAlarmMapper extends BaseMapperPlus<DeviceAlarm, DeviceAla
* @param bo 设备告警 * @param bo 设备告警
* @return 设备告警 * @return 设备告警
*/ */
Page<DeviceAlarmVo> selectVoPage( Page pageQuery,@Param("bo") DeviceAlarmBo bo); Page<DeviceAlarmVo> selectVoPage(Page pageQuery, @Param("bo") DeviceAlarmBo bo);
/** /**
* 根据设备IMEI查询最新的一条告警数据 * 根据设备IMEI查询最新的一条告警数据
@ -34,4 +40,25 @@ public interface DeviceAlarmMapper extends BaseMapperPlus<DeviceAlarm, DeviceAla
DeviceAlarmVo selectLatestByDeviceImei(@Param("deviceImei") String deviceImei); DeviceAlarmVo selectLatestByDeviceImei(@Param("deviceImei") String deviceImei);
/**
* 获取实时告警列表
*
* @return 设备告警列表
*/
List<DeviceAlarmVo> getRealtimeAlarm();
/**
* 获取报警统计数据
*
* @return 报警统计数据
*/
AlarmStatisticsVo getAlarmStatistics();
/**
* 获取最近一年每月告警统计数据
*
* @return 每月告警统计数据
*/
@MapKey("key")
List<Map<String, Object>> getMonthlyAlarmStatistics();
} }

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus; import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus;
import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord; import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo; import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@ -27,7 +28,25 @@ 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); List<DeviceFenceAccessRecordVo> selectVoPageWithFenceAndDeviceName(@Param(Constants.WRAPPER) Wrapper<DeviceFenceAccessRecord> wrapper,@Param("fenceName") String fenceName);
/**
* 分页查询围栏进出记录列表纯XML形式
*
* @param page 分页参数
* @param bo 查询条件
* @return 围栏进出记录分页列表
*/
Page<DeviceFenceAccessRecordVo> selectVoPageByXml(Page<DeviceFenceAccessRecord> page, @Param("bo") DeviceFenceAccessRecordBo bo);
/**
* 查询设备最新的围栏记录
*
* @param deviceId 设备ID
* @return 围栏记录
*/
DeviceFenceAccessRecordVo selectLatestRecordByDeviceId(String deviceId);
} }

View File

@ -105,6 +105,9 @@ public interface DeviceMapper extends BaseMapper<Device> {
*/ */
List<Map<String, Object>> getEquipmentUsageData(@Param("deviceTypeId") Long deviceTypeId, @Param("range") Integer range); List<Map<String, Object>> getEquipmentUsageData(@Param("deviceTypeId") Long deviceTypeId, @Param("range") Integer range);
// 在DeviceMapper.java中添加方法
DeviceOverviewVo getDeviceOverview();
// 在DeviceMapper.java中添加方法 // 在DeviceMapper.java中添加方法
int getUsageDataForMonth(@Param("deviceId") Long deviceId, int getUsageDataForMonth(@Param("deviceId") Long deviceId,
@Param("year") int year, @Param("year") int year,
@ -117,4 +120,19 @@ public interface DeviceMapper extends BaseMapper<Device> {
* @return 设备信息 * @return 设备信息
*/ */
Device selectDeviceByImei(@Param("deviceImei") String deviceImei); Device selectDeviceByImei(@Param("deviceImei") String deviceImei);
/**
* 获取设备通讯方式统计数据
*
* @return 通讯方式统计列表
*/
List<DeviceCommunicationModeStatisticsVo> getDeviceCommunicationModeStatistics();
/**
* 获取设备使用频次统计
*
* @param days 天数
* @return 设备使用频次统计列表
*/
List<DeviceUsageFrequencyVo> getDeviceUsageFrequency(@Param("days") int days);
} }

View File

@ -1,11 +1,13 @@
package com.fuyuanshen.equipment.service; package com.fuyuanshen.equipment.service;
import cn.hutool.core.lang.Dict;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.fuyuanshen.common.core.domain.PageResult; import com.fuyuanshen.common.core.domain.PageResult;
import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceType;
import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo;
import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.form.DeviceForm;
import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria;
@ -152,4 +154,49 @@ public interface DeviceService extends IService<Device> {
* @return 设备信息 * @return 设备信息
*/ */
Device selectDeviceByImei(String deviceImei); Device selectDeviceByImei(String deviceImei);
/**
* 获取设备总览数据
*
* @return 设备总览数据
*/
DeviceOverviewVo getDeviceOverview();
/**
* 获取设备通讯方式统计数据
*
* @return 通讯方式统计数据列表
*/
List<DeviceCommunicationModeStatisticsVo> getDeviceCommunicationModeStatistics();
/**
* 获取设备使用频次统计
*
* @param days 天数(近一个月: 30, 近半年: 180
* @return 设备使用频次统计列表
*/
List<DeviceUsageFrequencyVo> getDeviceUsageFrequency(int days);
/**
* 根据设备IMEI更新设备的经纬度信息
*
* @param deviceImei 设备IMEI
* @param longitude 经度
* @param latitude 纬度
* @return 是否更新成功
*/
boolean updateDeviceLocationByImei(String deviceImei, String longitude, String latitude);
/**
* 根据条件查询设备位置信息
*
* @param groupId 设备分组ID
* @param deviceType 设备类型
* @param deviceImei 设备IMEI
* @return 设备位置信息列表
*/
List<DeviceLocationVo> getDeviceLocationInfo(Long groupId, Long deviceType, String deviceImei);
} }

View File

@ -1,12 +1,15 @@
package com.fuyuanshen.equipment.service; package com.fuyuanshen.equipment.service;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.vo.AlarmStatisticsVo;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
import com.fuyuanshen.equipment.domain.vo.MonthlyAlarmStatisticsVo;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Collection;
import java.util.Map;
/** /**
* 设备告警Service接口 * 设备告警Service接口
@ -75,4 +78,24 @@ public interface IDeviceAlarmService {
DeviceAlarmVo queryLatestByDeviceImei(String deviceImei); DeviceAlarmVo queryLatestByDeviceImei(String deviceImei);
/**
* 获取实时告警列表
*
* @return 设备告警列表
*/
List<DeviceAlarmVo> getRealtimeAlarm();
/**
* 获取报警统计数据
*
* @return 报警统计数据
*/
AlarmStatisticsVo getAlarmStatistics();
/**
* 获取最近一年每月告警统计数据
*
* @return 每月告警统计数据
*/
MonthlyAlarmStatisticsVo getMonthlyAlarmStatistics();
} }

View File

@ -1,7 +1,10 @@
package com.fuyuanshen.equipment.service; package com.fuyuanshen.equipment.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo; import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
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;
@ -16,7 +19,7 @@ import java.util.List;
* @author Lion Li * @author Lion Li
* @date 2025-09-11 * @date 2025-09-11
*/ */
public interface IDeviceGeoFenceService { public interface IDeviceGeoFenceService extends IService<DeviceGeoFence> {
/** /**
* 查询电子围栏 * 查询电子围栏

View File

@ -0,0 +1,20 @@
package com.fuyuanshen.equipment.service;
import com.fuyuanshen.equipment.domain.vo.WeatherInfoVo;
/**天气信息服务接口
* @author: 默苍璃
* @date: 2025-09-2715:26
*/
public interface IWeatherService {
/**
* 根据经纬度获取天气信息
*
* @param latitude 纬度
* @param longitude 经度
* @return 天气信息
*/
WeatherInfoVo getWeatherByCoordinates(Double latitude, Double longitude);
}

View File

@ -12,7 +12,9 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo; import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.vo.AlarmStatisticsVo;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo; import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
import com.fuyuanshen.equipment.domain.vo.MonthlyAlarmStatisticsVo;
import com.fuyuanshen.equipment.domain.DeviceAlarm; import com.fuyuanshen.equipment.domain.DeviceAlarm;
import com.fuyuanshen.equipment.mapper.DeviceAlarmMapper; import com.fuyuanshen.equipment.mapper.DeviceAlarmMapper;
import com.fuyuanshen.equipment.service.IDeviceAlarmService; import com.fuyuanshen.equipment.service.IDeviceAlarmService;
@ -20,6 +22,7 @@ import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
/** /**
* 设备告警Service业务层处理 * 设备告警Service业务层处理
@ -161,4 +164,51 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService {
return baseMapper.selectLatestByDeviceImei(deviceImei); return baseMapper.selectLatestByDeviceImei(deviceImei);
} }
/**
* 获取实时告警列表
*
* @return 设备告警列表
*/
@Override
public List<DeviceAlarmVo> getRealtimeAlarm() {
return baseMapper.getRealtimeAlarm();
}
/**
* 获取报警统计数据
*
* @return 报警统计数据
*/
@Override
public AlarmStatisticsVo getAlarmStatistics() {
return baseMapper.getAlarmStatistics();
}
/**
* 获取最近一年每月告警统计数据
*
* @return 每月告警统计数据
*/
@Override
public MonthlyAlarmStatisticsVo getMonthlyAlarmStatistics() {
List<Map<String, Object>> result = baseMapper.getMonthlyAlarmStatistics();
MonthlyAlarmStatisticsVo vo = new MonthlyAlarmStatisticsVo();
if (result != null && !result.isEmpty()) {
Map<String, Object> data = result.get(0);
Map<String, Integer> monthlyStats = vo.getMonthlyStatistics();
for (Map.Entry<String, Object> entry : data.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// 将数据库查询结果转换为Integer类型
Integer count = (value != null) ? Integer.valueOf(value.toString()) : 0;
monthlyStats.put(key, count);
}
}
return vo;
}
} }

View File

@ -5,6 +5,7 @@ import com.alibaba.excel.util.DateUtils;
import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.dto.DeviceExcelExportDTO; import com.fuyuanshen.equipment.domain.dto.DeviceExcelExportDTO;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException; import java.io.IOException;
@ -20,6 +21,7 @@ import java.util.stream.Collectors;
* @date: 2025-06-0618:22 * @date: 2025-06-0618:22
*/ */
@Service @Service
@Slf4j
public class DeviceExportService { public class DeviceExportService {
public void export(List<Device> devices, HttpServletResponse response) { public void export(List<Device> devices, HttpServletResponse response) {
@ -36,22 +38,24 @@ public class DeviceExportService {
// 转换为DTO列表 // 转换为DTO列表
List<DeviceExcelExportDTO> dtoList = devices.stream().map(device -> { List<DeviceExcelExportDTO> dtoList = devices.stream().map(device -> {
DeviceExcelExportDTO dto = new DeviceExcelExportDTO(); DeviceExcelExportDTO dto = new DeviceExcelExportDTO();
dto.setId(device.getId()); // dto.setId(device.getId());
dto.setDeviceType(device.getDeviceType()); // dto.setDeviceType(device.getDeviceType());
dto.setCustomerName(device.getCustomerName()); // dto.setCustomerName(device.getCustomerName());
dto.setDeviceName(device.getDeviceName()); dto.setDeviceName(device.getDeviceName());
dto.setDeviceMac(device.getDeviceMac()); dto.setDeviceMac(device.getDeviceMac());
// 设备IMEI // 设备IMEI
dto.setDeviceImei(device.getDeviceImei()); dto.setDeviceImei(device.getDeviceImei());
// 蓝牙名称 // 蓝牙名称
dto.setBluetoothName(device.getBluetoothName()); dto.setBluetoothName(device.getBluetoothName());
dto.setLongitude(device.getLongitude()); // dto.setLongitude(device.getLongitude());
dto.setLatitude(device.getLatitude()); // dto.setLatitude(device.getLatitude());
dto.setRemark(device.getRemark()); dto.setRemark(device.getRemark());
dto.setTypeName(device.getTypeName()); dto.setTypeName(device.getTypeName());
dto.setCreateBy(device.getCreateByName()); dto.setCreateBy(device.getCreateByName());
Integer deviceStatus = device.getDeviceStatus(); Integer deviceStatus = device.getDeviceStatus();
dto.setDeviceStatus(deviceStatus == 1 ? "正常" : "失效"); Integer bindingStatus = device.getBindingStatus();
// dto.setDeviceStatus(deviceStatus == 1 ? "正常" : "失效");
dto.setBindingStatus(bindingStatus == 1 ? "已绑定" : "未绑定");
// 时间戳转换 // 时间戳转换
dto.setCreateTime(DateUtils.format(device.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); dto.setCreateTime(DateUtils.format(device.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
@ -72,17 +76,31 @@ public class DeviceExportService {
private void handleDevicePic(Device device, DeviceExcelExportDTO dto) { private void handleDevicePic(Device device, DeviceExcelExportDTO dto) {
String picUrl = device.getDevicePic(); String picUrl = device.getDevicePic();
log.info("处理设备图片设备ID: {}, 图片URL: {}", device.getId(), picUrl);
if (picUrl != null && !picUrl.trim().isEmpty()) { if (picUrl != null && !picUrl.trim().isEmpty()) {
try { try {
// 自动将HTTP转换为HTTPS以避免重定向问题
if (picUrl.startsWith("http://")) {
picUrl = "https://" + picUrl.substring(7);
log.info("自动将HTTP转换为HTTPS: {}", picUrl);
}
// 尝试创建URL对象会自动验证格式 // 尝试创建URL对象会自动验证格式
dto.setDevicePic(new URL(picUrl)); URL url = new URL(picUrl);
} catch (MalformedURLException e) {
dto.setDevicePic(url);
log.info("成功设置设备图片URL到DTO");
} catch (Exception e) {
// 不是有效URL时设置为null // 不是有效URL时设置为null
log.info("设置设备图片失败设备ID: {}, URL: {}, 错误: {}", device.getId(), picUrl, e.getMessage());
dto.setDevicePic(null); dto.setDevicePic(null);
} }
} else { } else {
log.info("设备没有设置图片设备ID: {}", device.getId());
dto.setDevicePic(null); dto.setDevicePic(null);
} }
} }
} }

View File

@ -54,7 +54,8 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec
@Override @Override
public TableDataInfo<DeviceFenceAccessRecordVo> queryPageList(DeviceFenceAccessRecordBo bo, PageQuery pageQuery) { public TableDataInfo<DeviceFenceAccessRecordVo> queryPageList(DeviceFenceAccessRecordBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<DeviceFenceAccessRecord> lqw = buildQueryWrapper(bo); LambdaQueryWrapper<DeviceFenceAccessRecord> lqw = buildQueryWrapper(bo);
Page<DeviceFenceAccessRecordVo> result = baseMapper.selectVoPageWithFenceAndDeviceName(pageQuery.build(), lqw); // Page<DeviceFenceAccessRecordVo> result = baseMapper.selectVoPageWithFenceAndDeviceName(pageQuery.build(), lqw);
Page<DeviceFenceAccessRecordVo> result = baseMapper.selectVoPageByXml(pageQuery.build(), bo);
return TableDataInfo.build(result); return TableDataInfo.build(result);
} }
@ -68,7 +69,7 @@ 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); return baseMapper.selectVoPageWithFenceAndDeviceName(lqw, bo.getFenceName());
} }
@ -85,6 +86,7 @@ public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRec
lqw.eq(bo.getAccuracy() != null, DeviceFenceAccessRecord::getAccuracy, bo.getAccuracy()); lqw.eq(bo.getAccuracy() != null, DeviceFenceAccessRecord::getAccuracy, bo.getAccuracy());
lqw.eq(bo.getEventTime() != null, DeviceFenceAccessRecord::getEventTime, bo.getEventTime()); lqw.eq(bo.getEventTime() != null, DeviceFenceAccessRecord::getEventTime, bo.getEventTime());
lqw.eq(bo.getCreateTime() != null, DeviceFenceAccessRecord::getCreateTime, bo.getCreateTime()); lqw.eq(bo.getCreateTime() != null, DeviceFenceAccessRecord::getCreateTime, bo.getCreateTime());
return lqw; return lqw;
} }

View File

@ -1,5 +1,6 @@
package com.fuyuanshen.equipment.service.impl; package com.fuyuanshen.equipment.service.impl;
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;
import com.fuyuanshen.common.core.utils.MapstructUtils; import com.fuyuanshen.common.core.utils.MapstructUtils;
@ -8,6 +9,7 @@ import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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.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;
@ -17,10 +19,12 @@ 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.DeviceGeoFenceMapper; import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService; import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService;
import com.fuyuanshen.equipment.service.IDeviceFenceStatusService; 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 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;
@ -37,7 +41,7 @@ import java.util.*;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@Service @Service
public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService { public class DeviceGeoFenceServiceImpl extends ServiceImpl<DeviceGeoFenceMapper, DeviceGeoFence> implements IDeviceGeoFenceService {
private final DeviceGeoFenceMapper baseMapper; private final DeviceGeoFenceMapper baseMapper;
@ -240,6 +244,9 @@ public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService {
recordBo.setFenceId(fence.getId()); recordBo.setFenceId(fence.getId());
recordBo.setLatitude(request.getLatitude()); recordBo.setLatitude(request.getLatitude());
recordBo.setLongitude(request.getLongitude()); recordBo.setLongitude(request.getLongitude());
String address = GetAddressFromLatUtil.getAdd(request.getLongitude().toString(), request.getLatitude().toString());
recordBo.setEventAddress(address);
recordBo.setEventTime(new Date()); recordBo.setEventTime(new Date());
// 1表示进入围栏2表示离开围栏 // 1表示进入围栏2表示离开围栏
recordBo.setEventType(currentStatus); recordBo.setEventType(currentStatus);

View File

@ -18,6 +18,7 @@ import com.fuyuanshen.equipment.domain.vo.DeviceRepairImagesVo;
import com.fuyuanshen.equipment.enums.RepairImageType; import com.fuyuanshen.equipment.enums.RepairImageType;
import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceRepairImagesMapper; import com.fuyuanshen.equipment.mapper.DeviceRepairImagesMapper;
import com.fuyuanshen.equipment.utils.FileHashUtil;
import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.system.service.ISysOssService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -32,6 +33,7 @@ import com.fuyuanshen.equipment.service.IDeviceRepairRecordsService;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*; import java.util.*;
/** /**
@ -178,6 +180,10 @@ public class DeviceRepairRecordsServiceImpl extends ServiceImpl<DeviceRepairReco
if (!updated) { if (!updated) {
return false; return false;
} }
// 3. 删除旧图片
if(bo.getImageIds() != null){
imagesMapper.deleteByIds(bo.getImageIds());
}
// 3. 收集需要保存的图片 // 3. 收集需要保存的图片
List<DeviceRepairImages> images = new ArrayList<>(2); List<DeviceRepairImages> images = new ArrayList<>(2);
@ -200,12 +206,28 @@ public class DeviceRepairRecordsServiceImpl extends ServiceImpl<DeviceRepairReco
if (file == null || file.isEmpty()) { if (file == null || file.isEmpty()) {
return; return;
} }
SysOssVo ossVo = ossService.upload(file);
// 1. 计算文件哈希
String hash = null;
try {
hash = FileHashUtil.hash(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 2. 先根据 hash 查库(秒传)
SysOssVo exist = ossService.selectByHash(hash);
if (exist == null) {
// 2.2 不存在,真正上传
exist = ossService.upload(file);
// 2.3 把 hash 写回记录(供下次去重)
ossService.updateHashById(exist.getOssId(), hash);
}
DeviceRepairImages image = new DeviceRepairImages(); DeviceRepairImages image = new DeviceRepairImages();
image.setRecordId(recordId); image.setRecordId(recordId);
image.setImageType(imageType); image.setImageType(imageType);
image.setImageUrl(ossVo.getUrl()); image.setImageUrl(exist.getUrl());
list.add(image); list.add(image);
} }

View File

@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fuyuanshen.common.core.domain.model.LoginUser; import com.fuyuanshen.common.core.domain.model.LoginUser;
import com.fuyuanshen.common.core.exception.BadRequestException; import com.fuyuanshen.common.core.exception.BadRequestException;
import com.fuyuanshen.common.core.utils.SpringUtils;
import com.fuyuanshen.common.core.utils.StringUtils; import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
@ -19,11 +20,10 @@ import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.customer.domain.Customer; import com.fuyuanshen.customer.domain.Customer;
import com.fuyuanshen.customer.mapper.CustomerMapper; import com.fuyuanshen.customer.mapper.CustomerMapper;
import com.fuyuanshen.equipment.constants.DeviceConstants; import com.fuyuanshen.equipment.constants.DeviceConstants;
import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.*;
import com.fuyuanshen.equipment.domain.DeviceAssignments; import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
import com.fuyuanshen.equipment.domain.DeviceType;
import com.fuyuanshen.equipment.domain.DeviceTypeGrants;
import com.fuyuanshen.equipment.domain.dto.AppDeviceBo; import com.fuyuanshen.equipment.domain.dto.AppDeviceBo;
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
import com.fuyuanshen.equipment.domain.form.DeviceForm; import com.fuyuanshen.equipment.domain.form.DeviceForm;
import com.fuyuanshen.equipment.domain.query.DeviceAssignmentQuery; import com.fuyuanshen.equipment.domain.query.DeviceAssignmentQuery;
import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria; import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria;
@ -32,13 +32,8 @@ import com.fuyuanshen.equipment.domain.vo.*;
import com.fuyuanshen.equipment.enums.BindingStatusEnum; import com.fuyuanshen.equipment.enums.BindingStatusEnum;
import com.fuyuanshen.equipment.enums.CommunicationModeEnum; import com.fuyuanshen.equipment.enums.CommunicationModeEnum;
import com.fuyuanshen.equipment.enums.DeviceActiveStatusEnum; import com.fuyuanshen.equipment.enums.DeviceActiveStatusEnum;
import com.fuyuanshen.equipment.mapper.DeviceAssignmentsMapper; import com.fuyuanshen.equipment.mapper.*;
import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.service.*;
import com.fuyuanshen.equipment.mapper.DeviceTypeGrantsMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.equipment.service.DeviceAssignmentsService;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.equipment.service.DeviceTypeGrantsService;
import com.fuyuanshen.system.domain.vo.SysOssVo; import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.domain.vo.SysRoleVo; import com.fuyuanshen.system.domain.vo.SysRoleVo;
import com.fuyuanshen.system.service.ISysOssService; import com.fuyuanshen.system.service.ISysOssService;
@ -88,6 +83,8 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
private final DeviceTypeGrantsService deviceTypeGrantsService; private final DeviceTypeGrantsService deviceTypeGrantsService;
private final DeviceTypeGrantsMapper deviceTypeGrantsMapper; private final DeviceTypeGrantsMapper deviceTypeGrantsMapper;
private final DeviceFenceAccessRecordMapper deviceFenceAccessRecordMapper;
/** /**
* 分页查询设备 * 分页查询设备
@ -139,8 +136,27 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
@Override @Override
public List<Device> queryAll(DeviceQueryCriteria criteria) { public List<Device> queryAll(DeviceQueryCriteria criteria) {
criteria.setCurrentOwnerId(LoginHelper.getUserId());
// 角色管理员
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;
}
}
}
// 只有非admin用户才设置当前用户ID条件
if (!isAdmin) {
criteria.setCurrentOwnerId(LoginHelper.getUserId());
}
return deviceMapper.findAll(criteria); return deviceMapper.findAll(criteria);
} }
@ -684,4 +700,120 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
return baseMapper.selectDeviceByImei(deviceImei); return baseMapper.selectDeviceByImei(deviceImei);
} }
/**
* 获取设备总览
*
* @return
*/
@Override
public DeviceOverviewVo getDeviceOverview() {
return deviceMapper.getDeviceOverview();
}
/**
* 获取设备通讯方式统计
*
* @return 设备通讯方式统计列表
*/
@Override
public List<DeviceCommunicationModeStatisticsVo> getDeviceCommunicationModeStatistics() {
return deviceMapper.getDeviceCommunicationModeStatistics();
}
@Override
public List<DeviceUsageFrequencyVo> getDeviceUsageFrequency(int days) {
return deviceMapper.getDeviceUsageFrequency(days);
}
/**
* 根据设备IMEI更新设备的经纬度信息
*
* @param deviceImei 设备IMEI
* @param longitude 经度
* @param latitude 纬度
* @return 是否更新成功
*/
@Override
public boolean updateDeviceLocationByImei(String deviceImei, String longitude, String latitude) {
// 根据设备IMEI查询设备
Device device = deviceMapper.selectDeviceByImei(deviceImei);
if (device == null) {
return false;
}
// 更新设备的经纬度信息
device.setLongitude(longitude);
device.setLatitude(latitude);
// 更新数据库中的设备信息
return deviceMapper.updateById(device) > 0;
}
/**
* 根据条件查询设备位置信息
*
* @param groupId 设备分组ID
* @param deviceType 设备类型
* @param deviceImei 设备IMEI
* @return 设备位置信息列表
*/
@Override
public List<DeviceLocationVo> getDeviceLocationInfo(Long groupId, Long deviceType, String deviceImei) {
// 构建查询条件
DeviceQueryCriteria criteria = new DeviceQueryCriteria();
criteria.setGroupId(groupId);
criteria.setDeviceType(deviceType);
criteria.setDeviceImei(deviceImei);
// 查询符合条件的设备
List<Device> devices = deviceMapper.findDevices(criteria);
// 构建返回结果
List<DeviceLocationVo> result = new ArrayList<>();
// 注入电子围栏服务
IDeviceGeoFenceService geoFenceService = SpringUtils.getBean(IDeviceGeoFenceService.class);
for (Device device : devices) {
DeviceLocationVo vo = new DeviceLocationVo();
vo.setDeviceId(device.getId());
vo.setDeviceName(device.getDeviceName());
vo.setLongitude(device.getLongitude());
vo.setLatitude(device.getLatitude());
// 检查设备是否在电子围栏内
if (StringUtils.isNotBlank(device.getLongitude()) && StringUtils.isNotBlank(device.getLatitude())) {
// 查询设备最新的围栏进出记录
DeviceFenceAccessRecordVo latestRecord = deviceFenceAccessRecordMapper.selectLatestRecordByDeviceId(String.valueOf(device.getId()));
// 判断是否在围栏内
if (latestRecord != null) {
// 如果最新的记录是进入围栏(事件类型为1),则设备在围栏内
if (latestRecord.getEventType() != null && latestRecord.getEventType() == 1L) {
vo.setInFence(true);
// 获取围栏完整信息
DeviceGeoFence fenceInfo = geoFenceService.getById(latestRecord.getFenceId());
if (fenceInfo != null) {
vo.setFenceInfo(fenceInfo);
}
} else {
vo.setInFence(false);
}
} else {
vo.setInFence(false);
}
} else {
vo.setInFence(false);
}
result.add(vo);
}
return result;
}
} }

View File

@ -98,18 +98,35 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
public List<DeviceType> queryDeviceTypes() { public List<DeviceType> queryDeviceTypes() {
DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria(); DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria();
// 管理员 // // 管理员
String username = LoginHelper.getUsername(); // String username = LoginHelper.getUsername();
if (!username.equals("admin")) { // if (!username.equals("admin")) {
criteria.setCustomerId(LoginHelper.getUserId()); // criteria.setCustomerId(LoginHelper.getUserId());
//
// Long userId = LoginHelper.getUserId();
// criteria.setCustomerId(userId);
// }
Long userId = LoginHelper.getUserId(); // 角色管理员
criteria.setCustomerId(userId); Long userId = LoginHelper.getUserId();
List<SysRoleVo> roles = roleService.selectRolesAuthByUserId(userId);
boolean isAdmin = false;
if (CollectionUtil.isNotEmpty(roles)) {
for (SysRoleVo role : roles) {
if (role.getRoleKey().contains("admin")) {
isAdmin = true;
break;
}
}
}
if (!isAdmin) {
criteria.setCustomerId(LoginHelper.getUserId());
} }
return deviceTypeMapper.findAll(criteria); return deviceTypeMapper.findAll(criteria);
} }
/** /**
* 根据设备类型名称查询设备类型 * 根据设备类型名称查询设备类型
* *
@ -119,7 +136,23 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
@Override @Override
public DeviceType queryByName(String typeName) { public DeviceType queryByName(String typeName) {
DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria(); DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria();
criteria.setCustomerId(LoginHelper.getUserId());
// 角色管理员
Long userId = LoginHelper.getUserId();
List<SysRoleVo> roles = roleService.selectRolesAuthByUserId(userId);
boolean isAdmin = false;
if (CollectionUtil.isNotEmpty(roles)) {
for (SysRoleVo role : roles) {
if (role.getRoleKey().contains("admin")) {
isAdmin = true;
break;
}
}
}
if (!isAdmin) {
criteria.setCustomerId(LoginHelper.getUserId());
}
criteria.setTypeName(typeName); criteria.setTypeName(typeName);
DeviceType deviceType = deviceTypeMapper.queryByName(criteria); DeviceType deviceType = deviceTypeMapper.queryByName(criteria);
return deviceType; return deviceType;

View File

@ -0,0 +1,82 @@
package com.fuyuanshen.equipment.service.impl;
import com.fuyuanshen.equipment.domain.vo.WeatherInfoVo;
import com.fuyuanshen.equipment.service.IWeatherService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**天气信息服务实现类
* @author: 默苍璃
* @date: 2025-09-2715:26
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class WeatherServiceImpl implements IWeatherService {
// 这里使用OpenWeatherMap API作为示例
private static final String WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={appid}&units=metric&lang=zh_cn";
// 需要在配置文件中配置API密钥
private final String apiKey = ""; // 从配置文件获取
@Override
public WeatherInfoVo getWeatherByCoordinates(Double latitude, Double longitude) {
try {
// 注意实际使用时需要配置API密钥并启用以下代码
/*
RestTemplate restTemplate = new RestTemplate();
String url = WEATHER_API_URL.replace("{lat}", latitude.toString())
.replace("{lon}", longitude.toString())
.replace("{appid}", apiKey);
// 调用第三方API获取天气数据
// 这里需要根据实际API返回的数据结构进行解析
// 以下为示例代码实际实现需要根据API文档调整
WeatherInfoVo weatherInfo = new WeatherInfoVo();
weatherInfo.setLatitude(latitude);
weatherInfo.setLongitude(longitude);
weatherInfo.setUpdateTime(LocalDateTime.now());
// 模拟数据实际应从API获取
weatherInfo.setWeatherCondition("晴");
weatherInfo.setDescription("晴朗");
weatherInfo.setTemperature(25.0);
weatherInfo.setFeelsLike(26.0);
weatherInfo.setHumidity(60);
weatherInfo.setPressure(1013.0);
weatherInfo.setVisibility(10000);
weatherInfo.setWindSpeed(2.5);
weatherInfo.setWindDirection(180);
weatherInfo.setCloudiness(0);
return weatherInfo;
*/
// 临时返回模拟数据
WeatherInfoVo weatherInfo = new WeatherInfoVo();
weatherInfo.setLatitude(latitude);
weatherInfo.setLongitude(longitude);
weatherInfo.setWeatherCondition("");
weatherInfo.setDescription("晴朗");
weatherInfo.setTemperature(25.0);
weatherInfo.setFeelsLike(26.0);
weatherInfo.setHumidity(60);
weatherInfo.setPressure(1013.0);
weatherInfo.setVisibility(10000);
weatherInfo.setWindSpeed(2.5);
weatherInfo.setWindDirection(180);
weatherInfo.setCloudiness(0);
weatherInfo.setUpdateTime(LocalDateTime.now());
return weatherInfo;
} catch (Exception e) {
log.error("获取天气信息失败,经纬度: {},{}", latitude, longitude, e);
return null;
}
}
}

View File

@ -1,4 +1,4 @@
package com.fuyuanshen.web.util; package com.fuyuanshen.equipment.utils;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;

View File

@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory;
import java.net.URL; import java.net.URL;
/** /**
* 根据经纬度获取地址信息工具类
*
* @author: 默苍璃 * @author: 默苍璃
* @date: 2025-07-2615:59 * @date: 2025-07-2615:59
*/ */
@ -74,5 +76,6 @@ public class GetAddressFromLatUtil {
System.out.println("通过API获取到具体位置:" + res); System.out.println("通过API获取到具体位置:" + res);
return res; return res;
} }
} }

View File

@ -14,7 +14,8 @@
left join device_type dt on dt.id = d.device_type left join device_type dt on dt.id = d.device_type
<where> <where>
<if test="bo.content != null"> <if test="bo.content != null">
and d.device_name like concat('%', #{bo.content}, '%') or dt.type_name like concat('%', #{bo.content}, '%') and d.device_name like concat('%', #{bo.content}, '%') or dt.type_name like concat('%', #{bo.content},
'%')
</if> </if>
<if test="bo.deviceName != null"> <if test="bo.deviceName != null">
and d.device_name like concat('%', #{bo.deviceName}, '%') and d.device_name like concat('%', #{bo.deviceName}, '%')
@ -29,9 +30,10 @@
and da.treatment_state = #{bo.treatmentState} and da.treatment_state = #{bo.treatmentState}
</if> </if>
<if test="bo.queryTime1 != null and bo.queryTime2 != null "> <if test="bo.queryTime1 != null and bo.queryTime2 != null ">
and da.start_time between #{bo.queryTime1} and #{bo.queryTime2} and da.start_time BETWEEN #{bo.queryTime1} AND #{bo.queryTime2}
</if> </if>
</where> </where>
order by da.create_time DESC
</select> </select>
@ -50,4 +52,52 @@
</select> </select>
<!-- 获取实时告警列表 -->
<select id="getRealtimeAlarm" resultType="com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo">
select da.*,
d.device_mac as deviceMac,
d.device_imei as deviceImei,
d.device_name as deviceName,
d.device_type as deviceType,
d.type_name as deviceTypeName,
d.device_pic as devicePic
from device_alarm da
left join device d on da.device_id = d.id
left join device_type dt on dt.id = d.device_type
WHERE da.treatment_state = 1
order by da.create_time DESC
</select>
<!-- 获取报警统计数据 -->
<select id="getAlarmStatistics" resultType="com.fuyuanshen.equipment.domain.vo.AlarmStatisticsVo">
SELECT
(SELECT COUNT(1) FROM device_alarm WHERE treatment_state = 1) AS activeAlarms,
(SELECT COUNT(1) FROM device_alarm) AS totalAlarms,
(SELECT COUNT(1) FROM device_alarm WHERE treatment_state = 0) AS processedAlarms,
(SELECT COUNT(1) FROM device_alarm WHERE device_action = 0) AS forcedAlarms,
(SELECT COUNT(1) FROM device_alarm WHERE device_action = 1) AS intrusionImpactAlarms,
(SELECT COUNT(1) FROM device_alarm WHERE device_action = 2) AS manualAlarms,
(SELECT COUNT(1) FROM device_alarm WHERE device_action = 3) AS geoFenceAlarms
</select>
<!-- 获取最近一年每月告警统计数据 -->
<select id="getMonthlyAlarmStatistics" resultType="map">
SELECT
COUNT(CASE WHEN MONTH(create_time) = 1 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m1,
COUNT(CASE WHEN MONTH(create_time) = 2 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m2,
COUNT(CASE WHEN MONTH(create_time) = 3 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m3,
COUNT(CASE WHEN MONTH(create_time) = 4 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m4,
COUNT(CASE WHEN MONTH(create_time) = 5 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m5,
COUNT(CASE WHEN MONTH(create_time) = 6 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m6,
COUNT(CASE WHEN MONTH(create_time) = 7 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m7,
COUNT(CASE WHEN MONTH(create_time) = 8 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m8,
COUNT(CASE WHEN MONTH(create_time) = 9 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m9,
COUNT(CASE WHEN MONTH(create_time) = 10 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m10,
COUNT(CASE WHEN MONTH(create_time) = 11 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m11,
COUNT(CASE WHEN MONTH(create_time) = 12 AND create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN 1 END) AS m12
FROM device_alarm
WHERE create_time >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
</select>
</mapper> </mapper>

View File

@ -8,22 +8,99 @@
<select id="selectVoPageWithFenceAndDeviceName" <select id="selectVoPageWithFenceAndDeviceName"
resultType="com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo"> resultType="com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo">
SELECT r.id, SELECT r.id,
r.fence_id, r.fence_id,
f.name AS fence_name, f.name AS fence_name,
r.device_id, r.device_id,
d.device_name, d.device_name,
r.user_id, r.user_id,
r.event_type, r.event_type,
r.latitude, r.latitude,
r.longitude, r.longitude,
r.accuracy, r.accuracy,
r.event_time, r.event_time,
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 LEFT JOIN device_geo_fence f ON r.fence_id = f.id
LEFT JOIN device d ON r.device_id = d.id LEFT JOIN device d ON r.device_id = d.id
${ew.customSqlSegment} ${ew.customSqlSegment}
<where>
<if test="fenceName != null and fenceName != ''">
AND f.name LIKE CONCAT('%', #{fenceName}, '%')
</if>
</where>
ORDER BY r.id ASC ORDER BY r.id ASC
</select> </select>
<!-- 分页查询围栏进出记录列表纯XML形式 -->
<select id="selectVoPageByXml" resultType="com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo">
SELECT r.id,
r.fence_id,
f.name AS fence_name,
r.device_id,
d.device_name,
r.user_id,
r.event_type,
r.latitude,
r.longitude,
r.accuracy,
r.event_time, r.event_address,
r.create_time
FROM device_fence_access_record r
LEFT JOIN device_geo_fence f ON r.fence_id = f.id
LEFT JOIN device d ON r.device_id = d.id
<where>
<if test="bo.fenceId != null">
AND r.fence_id = #{bo.fenceId}
</if>
<if test="bo.deviceId != null and bo.deviceId != ''">
AND r.device_id = #{bo.deviceId}
</if>
<if test="bo.userId != null">
AND r.user_id = #{bo.userId}
</if>
<if test="bo.eventType != null">
AND r.event_type = #{bo.eventType}
</if>
<if test="bo.latitude != null">
AND r.latitude = #{bo.latitude}
</if>
<if test="bo.longitude != null">
AND r.longitude = #{bo.longitude}
</if>
<if test="bo.accuracy != null">
AND r.accuracy = #{bo.accuracy}
</if>
<if test="bo.eventTime != null">
AND r.event_time = #{bo.eventTime}
</if>
<if test="bo.createTime != null">
AND r.create_time = #{bo.createTime}
</if>
<if test="bo.fenceName != null and bo.fenceName != ''">
AND f.name LIKE CONCAT('%', #{bo.fenceName}, '%')
</if>
<!-- 添加时间范围筛选条件 -->
<if test="bo.beginTime != null and bo.beginTime != ''">
AND r.event_time >= #{bo.beginTime}
</if>
<if test="bo.endTime != null and bo.endTime != ''">
AND r.event_time <![CDATA[ <= ]]> #{bo.endTime}
</if>
</where>
ORDER BY r.event_time DESC
</select>
<!-- 查询设备最新的围栏记录 -->
<select id="selectLatestRecordByDeviceId" resultType="com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo">
SELECT *
FROM device_fence_access_record
WHERE device_id = #{deviceId}
ORDER BY event_time DESC
LIMIT 1
</select>
</mapper> </mapper>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?> <?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" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fuyuanshen.equipment.mapper.DeviceMapper"> <mapper namespace="com.fuyuanshen.equipment.mapper.DeviceMapper">
<resultMap id="BaseResultMap" type="com.fuyuanshen.equipment.domain.Device"> <resultMap id="BaseResultMap" type="com.fuyuanshen.equipment.domain.Device">
<id column="id" property="id"/> <id column="id" property="id"/>
@ -239,8 +239,7 @@
where d.device_mac = #{deviceMac} where d.device_mac = #{deviceMac}
</select> </select>
<select id="queryWebDeviceList" resultType="com.fuyuanshen.equipment.domain.vo.WebDeviceVo"> <select id="queryWebDeviceList" resultType="com.fuyuanshen.equipment.domain.vo.WebDeviceVo">
select d.id, d.device_name, d.device_name, select * from (select d.id, d.device_name,
d.device_name,
d.device_mac, d.device_mac,
d.device_sn, d.device_sn,
d.device_imei, d.device_imei,
@ -252,40 +251,41 @@
ap.name personnelBy, ap.name personnelBy,
d.device_status, d.device_status,
d.online_status, d.online_status,
c.binding_time c.binding_time,
ROW_NUMBER() OVER (PARTITION BY d.id ORDER BY c.binding_time) 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
inner join app_device_bind_record c on d.id = c.device_id and c.communication_mode = 0 inner join app_device_bind_record c on d.id = c.device_id
left join app_personnel_info ap on ap.device_id = d.id left join app_personnel_info ap on ap.device_id = d.id
where dt.communication_mode in (0, 2) where dt.communication_mode in (0, 2) ) a where a.row_num = 1
<if test="criteria.deviceType != null"> <if test="criteria.deviceType != null">
and d.device_type = #{criteria.deviceType} and a.device_type = #{criteria.deviceType}
</if> </if>
<if test="criteria.deviceName != null and criteria.deviceName != ''"> <if test="criteria.deviceName != null and criteria.deviceName != ''">
and d.device_name like concat('%', #{criteria.deviceName}, '%') and a.device_name like concat('%', #{criteria.deviceName}, '%')
</if> </if>
<if test="criteria.deviceImei != null and criteria.deviceImei != ''"> <if test="criteria.deviceImei != null and criteria.deviceImei != ''">
and (d.device_imei = #{criteria.deviceImei} and a.device_imei = #{criteria.deviceImei}
</if> </if>
<if test="criteria.content != null and criteria.content != ''"> <if test="criteria.content != null and criteria.content != ''">
AND d.device_imei = #{criteria.content} or d.device_mac = #{criteria.content} AND (a.device_imei = #{criteria.content} or a.device_mac = #{criteria.content})
</if> </if>
<if test="criteria.deviceStatus != null"> <if test="criteria.deviceStatus != null">
and d.device_status = #{criteria.deviceStatus} and a.device_status = #{criteria.deviceStatus}
</if> </if>
<if test="criteria.personnelBy != null and criteria.personnelBy != ''"> <if test="criteria.personnelBy != null and criteria.personnelBy != ''">
and ap.name like concat('%', #{criteria.personnelBy}, '%') and a.personnelBy like concat('%', #{criteria.personnelBy}, '%')
</if> </if>
<if test="criteria.communicationMode != null"> <if test="criteria.communicationMode != null">
and dt.communication_mode = #{criteria.communicationMode} and a.communication_mode = #{criteria.communicationMode}
</if> </if>
<if test="criteria.groupId != null"> <if test="criteria.groupId != null">
and d.group_id = #{criteria.groupId} and a.group_id = #{criteria.groupId}
</if> </if>
<if test="criteria.onlineStatus != null"> <if test="criteria.onlineStatus != null">
and d.online_status = #{criteria.onlineStatus} and a.online_status = #{criteria.onlineStatus}
</if> </if>
ORDER BY d.create_time DESC ORDER BY a.binding_time DESC
</select> </select>
<select id="getLocationHistory" resultType="com.fuyuanshen.equipment.domain.vo.LocationHistoryVo"> <select id="getLocationHistory" resultType="com.fuyuanshen.equipment.domain.vo.LocationHistoryVo">
select a.id,a.device_name,a.device_type,b.type_name deviceTypeName,a.device_imei,a.device_mac from device a select a.id,a.device_name,a.device_type,b.type_name deviceTypeName,a.device_imei,a.device_mac from device a
@ -319,6 +319,32 @@
a.create_time DESC a.create_time DESC
</select> </select>
<!-- 获取设备总览信息 -->
<select id="getDeviceOverview" resultType="com.fuyuanshen.equipment.domain.vo.DeviceOverviewVo">
SELECT (SELECT COUNT(1) FROM device) AS totalDevices,
(SELECT COUNT(1) FROM device WHERE online_status = 1) AS onlineDevices,
(SELECT COUNT(DISTINCT device_type) FROM device) AS deviceTypes
</select>
<!-- 获取设备通讯方式统计数据 -->
<select id="getDeviceCommunicationModeStatistics" resultType="com.fuyuanshen.equipment.domain.vo.DeviceCommunicationModeStatisticsVo">
SELECT
dt.communication_mode AS communicationModeValue,
CASE
WHEN dt.communication_mode = 0 THEN '4G'
WHEN dt.communication_mode = 1 THEN '蓝牙'
WHEN dt.communication_mode = 2 THEN '4G&amp;蓝牙'
ELSE '未知'
END AS communicationModeName,
COUNT(d.id) AS totalDevices,
COUNT(CASE WHEN d.online_status = 2 THEN 1 END) AS abnormalDevices
FROM device_type dt
LEFT JOIN device d ON dt.id = d.device_type
WHERE dt.communication_mode IN (0, 1, 2)
GROUP BY dt.communication_mode
ORDER BY dt.communication_mode
</select>
<!-- 获取数据总览 --> <!-- 获取数据总览 -->
<select id="getDataOverview" resultType="com.fuyuanshen.equipment.domain.vo.DataOverviewVo"> <select id="getDataOverview" resultType="com.fuyuanshen.equipment.domain.vo.DataOverviewVo">
SELECT (SELECT COUNT(1) FROM device) AS devicesNumber, SELECT (SELECT COUNT(1) FROM device) AS devicesNumber,
@ -423,6 +449,17 @@
AND MONTH (dl.create_time) = #{month} AND MONTH (dl.create_time) = #{month}
</select> </select>
<!-- 获取设备使用频次统计 -->
<select id="getDeviceUsageFrequency" resultType="com.fuyuanshen.equipment.domain.vo.DeviceUsageFrequencyVo">
SELECT
device_name AS deviceName,
COUNT(*) AS frequency
FROM device_log
WHERE create_time >= DATE_SUB(NOW(), INTERVAL #{days} DAY)
GROUP BY device_name
ORDER BY frequency DESC
</select>
<!-- 根据设备IMEI查询设备 --> <!-- 根据设备IMEI查询设备 -->
<select id="selectDeviceByImei" resultType="com.fuyuanshen.equipment.domain.Device"> <select id="selectDeviceByImei" resultType="com.fuyuanshen.equipment.domain.Device">
SELECT * SELECT *

View File

@ -23,6 +23,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
FROM device_repair_records dr FROM device_repair_records dr
JOIN device d ON dr.device_id = d.id JOIN device d ON dr.device_id = d.id
<where> <where>
<if test="criteria.searchValue != null">
and d.device_name like concat('%', TRIM(#{criteria.searchValue}), '%')
</if>
<if test="criteria.deviceId != null"> <if test="criteria.deviceId != null">
and dr.device_id = #{criteria.deviceId} and dr.device_id = #{criteria.deviceId}
</if> </if>

View File

@ -49,8 +49,13 @@
parameterType="com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria"> parameterType="com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria">
SELECT dt.*, dg.id AS grant_id SELECT dt.*, dg.id AS grant_id
FROM device_type dt FROM device_type dt
JOIN device_type_grants dg ON dt.id = dg.device_type_id JOIN device_type_grants dg ON dt.id = dg.device_type_id
WHERE dt.type_name = #{criteria.typeName} <where>
AND dg.customer_id = #{criteria.customerId} dt.type_name = #{criteria.typeName}
<if test="criteria.customerId != null">
and dg.customer_id = #{criteria.customerId}
</if>
</where>
</select> </select>
</mapper> </mapper>