0
0

163 Commits

Author SHA1 Message Date
df28eed305 prod配置文件 2025-10-28 10:51:05 +08:00
07e60cf7f0 Merge branch 'dyf-device' into 6170 2025-10-28 10:33:15 +08:00
5d42f8a1e1 Merge branch 'jingquan' into dyf-device 2025-10-28 10:32:31 +08:00
dyf
f2a5e63a41 Merge pull request 'feat(equipment): 添加阿里巴巴TTS语音合成工具类' (#18) from liwenlong/fys-Multi-tenant:jingquan into jingquan
Reviewed-on: dyf/fys-Multi-tenant#18
2025-10-28 10:31:37 +08:00
467a7befb1 提交2 2025-10-28 10:29:12 +08:00
107ddf8851 feat(app): 添加防重复提交注解
- 在视频上传接口添加 @RepeatSubmit 注解
- 在音频上传接口添加 @RepeatSubmit 注解
- 在文字转音频接口添加 @RepeatSubmit 注解
- 设置重复提交间隔为2 秒- 添加重复提交提示信息"请勿重复提交!"
2025-10-27 09:00:36 +08:00
a85e74c5e6 在线状态任务3 2025-10-25 08:35:25 +08:00
9bbddee1d5 feat(equipment): 添加阿里巴巴TTS语音合成工具类
- 实现文本转语音功能,支持多种声音、语速、音量等参数调节
- 集成阿里云TTS服务,支持访问令牌自动刷新与缓存
- 提供HTTP客户端配置与请求处理逻辑
- 支持生成标准PCM数据及WAV格式音频文件
- 实现音频文件保存与错误处理机制
- 添加参数校验与日志记录功能
- 集成Redis缓存管理访问令牌- 支持URL编码与请求构建逻辑
- 实现响应处理与音频数据写入文件功能
- 添加静默删除临时文件与错误响应处理机制
2025-10-24 11:22:35 +08:00
da0833a400 Merge remote-tracking branch 'origin/6170' into 6170 2025-10-13 16:15:26 +08:00
418fb55bf0 导出电子围栏列表 2025-10-13 14:25:07 +08:00
1d50981a84 在线状态任务2 2025-10-13 14:14:51 +08:00
2f80c450c5 最多同时加载5张图片 2025-10-13 10:48:53 +08:00
09cbdf267b 设备绑定提示 2025-10-12 13:37:18 +08:00
156fdbb53b 历史轨迹mac地址支持模糊查询 2025-10-11 16:33:57 +08:00
3246127893 设备解绑 2025-10-11 16:16:06 +08:00
dab0440128 绑定bug修复 2025-10-11 16:07:14 +08:00
dyf
740a638444 Merge pull request 'fix(device):修复SOS报警类型判断逻辑' (#17) from liwenlong/fys-Multi-tenant:jingquan into jingquan
Reviewed-on: dyf/fys-Multi-tenant#17
2025-10-11 13:35:08 +08:00
a3d44a157a fix(device):修复SOS报警类型判断逻辑
- 简化报警类型判断,固定为SOS类型
- 移除不必要的shake类型判断逻辑
2025-10-11 13:23:51 +08:00
cc23f082a6 Merge remote-tracking branch 'upstream/6170' into 6170 2025-10-11 11:06:15 +08:00
1bc7dc676e 控制中心查询 2025-10-11 11:06:07 +08:00
5cdacad468 获取设备使用频次统计 2025-10-11 10:32:16 +08:00
62af38cbf0 Merge branch 'dyf-device' into 6170 2025-10-11 09:05:38 +08:00
04a21567aa 设备所属分组 2025-10-11 09:04:48 +08:00
5b4fa38dbd 设备告警 2025-10-10 15:46:25 +08:00
2c3effa683 历史轨迹1 2025-10-10 09:26:52 +08:00
7b8c626cb6 Merge branch 'jingquan' into dyf-device 2025-10-09 14:45:15 +08:00
dyf
9642908035 Merge pull request 'feat(app): 实现系统版本管理功能-Dto 新增 SystemVersion 数据传输对象- 修改 AppAuthController 的 getAppVersion 接口,从 Redis 获取版本信息' (#16) from liwenlong/fys-Multi-tenant:jingquan into jingquan
Reviewed-on: dyf/fys-Multi-tenant#16
2025-10-09 14:44:18 +08:00
85b2e2b976 设备告警1 2025-10-09 13:31:37 +08:00
88ac0236bd feat(app): 实现系统版本管理功能-Dto 新增 SystemVersion 数据传输对象- 修改 AppAuthController 的 getAppVersion 接口,从 Redis 获取版本信息
- 新增 DeviceXinghanController 的 VersionSettings 接口,用于更新版本信息到 Redis- 引入 FastJSON 和 Redis 工具类支持版本信息的序列化与缓存
- 添加权限注解控制版本更新接口访问
- 定义全局 Redis 键常量用于版本信息存储
2025-10-09 11:09:07 +08:00
609a3e4058 设备在线状态2 2025-10-09 09:44:48 +08:00
68cf78f3f2 设备在线状态5 2025-10-08 17:27:43 +08:00
c9857a679c 设备在线状态4 2025-10-08 15:08:31 +08:00
6439a96e3d 设备在线状态3 2025-10-08 15:07:54 +08:00
9f0155a6e3 Merge branch 'dyf-device' into 6170 2025-10-08 14:12:09 +08:00
6c99cef65d 解除告警 2025-10-08 14:11:11 +08:00
840c4fd68f 设备在线状态2 2025-10-08 13:53:03 +08:00
dc0fe96652 设备在线状态 2025-10-08 13:38:07 +08:00
cf883bfa00 Merge remote-tracking branch 'upstream/6170' into 6170 2025-10-08 11:48:16 +08:00
6a6397da23 控制查询条件加模糊查询 2025-10-08 11:48:05 +08:00
d701a834b3 设备IMEI 模糊查询 2025-10-08 09:37:18 +08:00
e1ac87e5f2 在线状态 2025-10-07 16:11:06 +08:00
5c3a35c83e 在线状态 2025-10-07 16:09:59 +08:00
2c0eff2b2c Merge remote-tracking branch 'origin/6170' into 6170 2025-10-07 15:50:26 +08:00
37e07d2706 控制中心优化2 2025-10-07 15:50:16 +08:00
dyf
5c0c4e40e4 Merge pull request 'fix(device):优化Redis缓存更新逻辑' (#15) from liwenlong/fys-Multi-tenant:jingquan into jingquan
Reviewed-on: dyf/fys-Multi-tenant#15
2025-10-07 15:46:56 +08:00
eceedae676 fix(device):优化Redis缓存更新逻辑
- 删除原有的直接设置缓存列表逻辑
- 新增先删除后写入的缓存更新策略- 统一使用变量存储Redis键值避免重复拼接- 保持原有缓存过期时间设置为5分钟
2025-10-07 15:29:19 +08:00
dfa5b446f9 控制中心优化 2025-10-07 09:50:49 +08:00
9afc0222d5 app批量上传logo 2025-09-30 17:53:28 +08:00
2cfcea65f1 Merge branch '6170' into dyf-device 2025-09-30 16:10:13 +08:00
dyf
9b72a6ebd2 Merge pull request 'feat(device): 新增设备类型查询与设备添加功能' (#14) from liwenlong/fys-Multi-tenant:jingquan into jingquan
Reviewed-on: dyf/fys-Multi-tenant#14
2025-09-30 16:06:10 +08:00
13db094336 feat(device): 新增设备类型查询与设备添加功能
- 在AppDeviceXinghanController中新增查询所有设备类型接口
- 实现新增设备功能,包含设备MAC和IMEI唯一性校验- 添加设备类型权限验证逻辑
- 完善设备绑定状态和主题设置
- 在DeviceXinghanBizService中实现设备分配记录保存
- 优化文件批量插入逻辑,支持是否批量删除历史数据- 增加文件删除功能,支持根据ID列表删除业务文件
- 缩短MQTT消息去重时间窗口至3秒
- 在设备维修记录查询条件中增加维修人员模糊查询- 调整设备消息发送数据顺序,单位名称移至首位
2025-09-30 15:59:24 +08:00
a1d6604520 app产品参数修改 2025-09-30 15:42:59 +08:00
b0e51e73c7 岗位信息1 2025-09-30 13:21:43 +08:00
dba537b83f 发送信息和告警故障5 2025-09-30 09:57:45 +08:00
d9b69ddc4e 不同的一级目录下的分组应当可以重名 2025-09-29 17:53:26 +08:00
6cec86bafe Merge remote-tracking branch 'upstream/6170' into jingquan 2025-09-29 13:57:29 +08:00
608aa8449a Merge branch 'main' into dyf-device 2025-09-29 09:26:49 +08:00
dyf
6084f11e62 Merge pull request 'jingquan' (#13) from liwenlong/fys-Multi-tenant:jingquan into main
Reviewed-on: dyf/fys-Multi-tenant#13
2025-09-29 09:26:04 +08:00
994e63bcde Merge branch '6170' of http://47.107.152.87:3000/dyf/fys-Multi-tenant into 6170 2025-09-29 09:24:52 +08:00
d6520d777a 发送信息和告警故障4 2025-09-29 09:17:20 +08:00
1898fe5db9 Merge branch '6170' into dyf-device 2025-09-28 18:19:21 +08:00
7d56e2e80e 发送信息和告警故障3 2025-09-28 17:18:47 +08:00
233e0e32b0 发送信息和告警故障2 2025-09-28 16:19:28 +08:00
cb2acdb3f4 Merge remote-tracking branch 'liwenlong-fys/jingquan' into jingquan 2025-09-28 16:11:52 +08:00
a4596b9c90 feat(device): 实现设备批量控制指令发送功能- 新增批量发送设备控制指令方法 sendCommandBatch- 支持设备离线状态检查和异常处理
- 添加设备操作日志记录和报警创建- 实现设备SOS档位批量设置接口
- 在设备指令处理中增加消息去重机制
- 优化设备报警处理的分布式锁逻辑
- 完善设备数据规则中的并发控制
2025-09-28 16:11:34 +08:00
5230a95865 自动报警 2025-09-28 15:28:19 +08:00
4d2b7c6adf 设备是否正在告警 2025-09-28 11:53:24 +08:00
461fd9364c Merge remote-tracking branch 'upstream/6170' into 6170 2025-09-27 15:40:39 +08:00
ad81647939 发送信息和告警故障 2025-09-27 15:40:31 +08:00
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: dyf/fys-Multi-tenant#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
5f36c25550 内容(报警信息) 2025-09-20 14:35:23 +08:00
9058dcfcb1 设备告警 2025-09-20 11:45:11 +08:00
3e19cd9db2 设置图片路径 2025-09-19 09:43:52 +08:00
397f227bfb Merge branch 'dyf-device' into 6170 2025-09-18 15:26:04 +08:00
1b2bf542fa 个人中心修改密码2 2025-09-18 15:25:32 +08:00
7d4bc1f8ad Merge branch 'liwenlong-jingquan'
# Conflicts:
#	fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java
#	fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java
#	fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml
#	fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml
2025-09-18 12:03:43 +08:00
4d9038567f 根据提供的代码差异信息,以下是符合Angular commit规范的commit message:
```
feat(app): 添加获取app版本接口及视频处理控制器

新增功能:
- 在AppAuthController中添加了获取app版本信息的接口`/version`。- 新增AppVideoController用于处理视频上传和帧提取。

修改内容:
- 在AppAuthController中引入了ISysDictTypeService服务。
- 在DeviceBizService中更新了设备通信模式的判断逻辑。
- 修改了DeviceAlarmMapper.xml和DeviceMapper.xml中的SQL查询语句以支持更多通信模式。
- 更新了DeviceXinghanBizService中的人员信息登记逻辑,并添加了获取设备详细信息的方法。
- 在DeviceXinghanController中添加了获取设备详细信息的接口`/info/{id}`。
- 更新了MqttXinghanJson类中的字段命名。
- 在pom.xml中添加了javacv相关的依赖。

修复问题:
- 注释掉了AppSmsAuthStrategy中的登录检查逻辑。
```

这个commit message包含了类型(feat)、作用范围(app)以及简短的描述。同时在body部分详细说明了新增的功能、修改的内容以及修复的问题。
2025-09-18 11:40:12 +08:00
630c6a8a35 角色管理员 2025-09-18 11:25:40 +08:00
11e48acc81 个人中心修改密码 2025-09-18 08:54:29 +08:00
540a6fc1da 密码长度验证 2025-09-17 17:43:17 +08:00
aeea8f9072 登录优化 2025-09-17 10:56:25 +08:00
b463e97d28 配置文件mqtt端口修改 2025-09-16 17:35:29 +08:00
e1b147b6be Merge branch 'dyf-device' into 6170 2025-09-16 17:05:19 +08:00
23b48cfb06 设备告警 2025-09-16 17:04:45 +08:00
4106260e5f 在线状态修改优化4 2025-09-15 17:49:28 +08:00
07449e4a1e 查询围栏进出记录列表 2025-09-15 16:49:38 +08:00
b278a3087e 报警事项 2025-09-15 14:59:08 +08:00
dca5a6c48d 在线状态修改优化3 2025-09-15 09:43:15 +08:00
15b3348989 Merge branch 'dyf-device' into 6170 2025-09-15 09:41:56 +08:00
9fbb0aefcf 检查设备位置与围栏的关系 2025-09-15 09:41:10 +08:00
4004aa1090 在线状态修改优化2 2025-09-13 16:51:56 +08:00
39c8375f08 在线状态修改优化 2025-09-13 16:46:05 +08:00
64c81ac44e 在线状态修改2 2025-09-13 15:51:34 +08:00
41eb3d9a33 在线状态修改 2025-09-13 15:37:48 +08:00
8597dc5a9f 导出围栏进出记录列表 2025-09-12 14:50:42 +08:00
01a1a6e25b 查询围栏进出记录 2025-09-12 11:48:39 +08:00
1722f92328 电子围栏-位置检查 2025-09-12 09:04:47 +08:00
ee8840d657 Merge remote-tracking branch 'origin/6170' into 6170 2025-09-11 14:43:02 +08:00
9b84296419 Merge branch 'main' into dyf-device 2025-09-11 14:41:41 +08:00
dyf
d48c7b389e Merge pull request 'jingquan' (#10) from liwenlong/fys-Multi-tenant:jingquan into main
Reviewed-on: dyf/fys-Multi-tenant#10
2025-09-11 14:40:23 +08:00
ccdc25595d Merge remote-tracking branch 'origin/6170' into 6170 2025-09-11 14:38:06 +08:00
f1aad91421 修改绑定1 2025-09-11 14:37:40 +08:00
e2274bdf09 feat(web): 新增设备联调中心功能
- 新增设备联调中心相关控制器、服务、DTO和VO
- 实现设备列表查询、文件上传、操作视频添加、设备详情等功能
- 优化设备 logo 上传逻辑,支持批量上传
- 重构部分代码结构,提高可维护性
2025-09-11 11:07:58 +08:00
8a127b9d2d Merge branch 'dyf-device' into 6170 2025-09-11 10:56:29 +08:00
b8af6b511c 电子围栏 2025-09-11 10:54:56 +08:00
6bc1d5b20b 绑定设备 2025-09-10 09:56:39 +08:00
228e26df7f merge upstream 2025-09-09 17:23:23 +08:00
801b0267e6 设备分享3 2025-09-09 17:01:51 +08:00
fcbde4322d 今日报警总数 2025-09-09 16:39:43 +08:00
832234269d 获取设备使用数据 2025-09-09 16:06:34 +08:00
7e87971c0b 新增绑定设备 2025-09-09 15:02:46 +08:00
ebd8668178 在线设备 2025-09-09 11:03:38 +08:00
5e3307d2b0 设备分享2 2025-09-09 10:29:51 +08:00
040e44984e Merge branch 'dyf-device' into 6170
# Conflicts:
#	fys-admin/src/main/java/com/fuyuanshen/web/controller/device/DeviceShareController.java
2025-09-09 10:01:26 +08:00
f7a82ef138 设备操作记录 分页 2025-09-09 09:55:58 +08:00
91f787eec7 pageQuery 2025-09-08 17:37:01 +08:00
870f94b2d4 开始时间 2025-09-08 16:12:02 +08:00
395b2957c1 Merge remote-tracking branch 'origin/6170' into 6170 2025-09-06 16:19:35 +08:00
64a4485ced 设备分享 2025-09-06 16:19:12 +08:00
1e4ee840a3 Merge branch 'dyf-device' into 6170 2025-09-06 11:48:00 +08:00
374a03b83d 管理员 2025-09-06 11:46:13 +08:00
b945420446 离线状态变更3 2025-09-05 13:32:32 +08:00
c057af7cd0 离线状态变更2 2025-09-05 11:46:29 +08:00
452c37c4ca 离线状态变更 2025-09-04 18:40:08 +08:00
d5a9a92f9f feat(device): 新增设备控制相关接口
- 人员信息登记- 发送紧急通知
-上传设备 logo 图片
-静电预警档位设置- 照明档位设置- SOS 档位设置- 静止报警状态设置
2025-09-04 11:50:18 +08:00
429c0f1307 Merge branch 'dyf-device' into 6170 2025-09-04 11:29:55 +08:00
ff4db34e2a 型号字典用于APP页面跳转 2025-09-04 11:29:07 +08:00
377579dd14 Merge branch 'dyf-device' into 6170 2025-09-03 15:57:21 +08:00
00e345d5e5 Merge branch 'main' into dyf-device 2025-09-03 15:56:30 +08:00
dyf
1a2f35a092 Merge pull request 'jingquan' (#9) from liwenlong/fys-Multi-tenant:jingquan into main
Reviewed-on: dyf/fys-Multi-tenant#9
2025-09-03 15:55:27 +08:00
bdbbd5a12f feat(equipment): 添加设备维修图片功能并优化维修记录相关接口- 新增 DeviceRepairImages 相关实体、Mapper 和 XML 文件- 重构 DeviceRepairRecords 相关接口和实现类,支持图片上传
- 新增 RepairImageType 枚举类
- 优化 DeviceRepairRecordsVo,增加图片列表和设备名称字段
2025-09-03 15:17:39 +08:00
98cb67b136 修改获取经纬度 2025-09-03 15:11:24 +08:00
d97928b38a 分配客户 接口注释 2025-09-03 09:45:17 +08:00
1d37bfb719 首页数据 2025-09-02 15:08:53 +08:00
98ef841f18 通讯方式 2025-09-02 10:32:17 +08:00
8462fed747 merge upstream 2025-09-02 09:06:54 +08:00
7aa02635f2 feat(device): 优化设备人员信息发送逻辑
- 调整人员信息的顺序,符合兴汉设备接收数据的要求
- 添加设备上报人员登记信息的日志,方便问题追踪
2025-08-27 17:30:00 +08:00
626296adbc merge upstream 2025-08-27 15:28:40 +08:00
9bbed77170 refactor(fys-demo): 优化视频上传接口并修复 MQTT 报警信息发送逻辑
- 移除了 VideoUploadController 中的 @SaIgnore 注解,恢复权限控制- 优化了 XinghanSendAlarmMessageRule 中的 Redis 数据检查逻辑
2025-08-27 15:27:19 +08:00
a5b8cdffec refactor(device): 优化设备发送告警信息功能
- 批量查询设备,减少数据库交互次数
- 优化设备状态检查逻辑,提高效率
- 封装单个设备发送告警信息的逻辑,提高代码可读性- 使用 Redis 和 MQTT 时增加异常处理,提高系统稳定性
- 优化日志记录和异常提示,便于问题排查
2025-08-26 17:12:36 +08:00
f839883f82 feat(device): 新增发送紧急通知功能
- 在 AppDeviceXinghanController 中添加 sendAlarmMessage接口
- 在 DeviceXinghanBizService 中实现 sendAlarmMessage 方法
- 新增 XinghanSendAlarmMessageRule 类用于处理紧急通知发送逻辑
- 在 DeviceRedisKeyConstants 中添加 DEVICE_ALARM_MESSAGE_KEY_PREFIX 常量
- 修改 XinghanDeviceDataRule 和 XinghanSendMsgRule 中的相关逻辑
2025-08-25 14:18:54 +08:00
206 changed files with 9849 additions and 1130 deletions

View File

@ -94,10 +94,10 @@
</dependency>
<!-- demo模块 -->
<!-- <dependency> -->
<!-- <groupId>com.fuyuanshen</groupId> -->
<!-- <artifactId>fys-demo</artifactId> -->
<!-- </dependency> -->
<!--<dependency>
<groupId>com.fuyuanshen</groupId>
<artifactId>fys-demo</artifactId>
</dependency>-->
<!-- 工作流模块 -->
<dependency>
@ -123,6 +123,17 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.7</version>
<scope>compile</scope>
</dependency>
<!-- skywalking 整合 logback -->
<!-- <dependency>-->
<!-- <groupId>org.apache.skywalking</groupId>-->

View File

@ -3,6 +3,7 @@ package com.fuyuanshen;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动程序
@ -10,6 +11,7 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt
* @author Lion Li
*/
@SpringBootApplication
@EnableScheduling
public class DromaraApplication {
public static void main(String[] args) {

View File

@ -4,28 +4,43 @@ import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.fuyuanshen.app.model.AppRegisterBody;
import com.fuyuanshen.app.model.AppSmsLoginBody;
import com.fuyuanshen.app.service.AppLoginService;
import com.fuyuanshen.app.service.AppRegisterService;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.constant.SystemConstants;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.domain.model.AppLoginBody;
import com.fuyuanshen.common.core.domain.model.AppSmsRegisterBody;
import com.fuyuanshen.common.core.domain.model.PasswordLoginBody;
import com.fuyuanshen.common.core.domain.model.AppPasswordLoginBody;
import com.fuyuanshen.common.core.domain.model.RegisterBody;
import com.fuyuanshen.common.core.domain.model.SmsLoginBody;
import com.fuyuanshen.common.core.enums.LoginType;
import com.fuyuanshen.common.core.utils.*;
import com.fuyuanshen.common.encrypt.annotation.ApiEncrypt;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.ratelimiter.annotation.RateLimiter;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.common.tenant.helper.TenantHelper;
import com.fuyuanshen.system.domain.bo.SysTenantBo;
import com.fuyuanshen.system.domain.vo.SysClientVo;
import com.fuyuanshen.system.domain.vo.SysDictDataVo;
import com.fuyuanshen.system.domain.vo.SysTenantVo;
import com.fuyuanshen.system.service.ISysClientService;
import com.fuyuanshen.system.service.ISysConfigService;
import com.fuyuanshen.system.service.ISysDictTypeService;
import com.fuyuanshen.system.service.ISysTenantService;
import com.fuyuanshen.web.domain.Dto.SystemVersionDto;
import com.fuyuanshen.web.domain.vo.LoginTenantVo;
import com.fuyuanshen.web.domain.vo.LoginVo;
import com.fuyuanshen.web.domain.vo.TenantListVo;
import com.fuyuanshen.web.service.AppRegisterService;
import com.fuyuanshen.web.service.IAuthStrategy;
import com.fuyuanshen.web.service.SysRegisterService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
@ -35,8 +50,14 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import static com.fuyuanshen.common.core.constant.GlobalConstants.DEVICE_SHARE_CODES_KEY;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
/**
* APP认证
*
@ -54,6 +75,7 @@ public class AppAuthController {
private final ISysConfigService configService;
private final ISysTenantService tenantService;
private final ISysClientService clientService;
private final ISysDictTypeService dictTypeService;
/**
@ -63,18 +85,36 @@ public class AppAuthController {
*/
// @ApiEncrypt
@PostMapping("/login")
public R<LoginVo> login(@RequestBody AppLoginBody appLoginBody) {
public R<LoginVo> login(@RequestBody AppSmsLoginBody appSmsLoginBody) {
// SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(appLoginBody);
PasswordLoginBody loginBody = new PasswordLoginBody();
loginBody.setUsername(appLoginBody.getUserName());
loginBody.setPassword(appLoginBody.getPassword());
loginBody.setTenantId(appLoginBody.getTenantId());
loginBody.setClientId("835b15335d389c9fcfdf99421fa8019b");
loginBody.setGrantType("appPassword");
String loginType = appSmsLoginBody.getLoginType();
String body = "";
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
String clientId = "";
String grantType = "";
String tenantId = appSmsLoginBody.getTenantId();
ValidatorUtils.validate(appSmsLoginBody);
if("2".equals(loginType)){
AppPasswordLoginBody loginBody = new AppPasswordLoginBody();
loginBody.setUsername(appSmsLoginBody.getPhonenumber());
loginBody.setPassword(appSmsLoginBody.getLoginPassword());
loginBody.setClientId("835b15335d389c9fcfdf99421fa8019b");
loginBody.setGrantType("appPassword");
clientId = loginBody.getClientId();
grantType = loginBody.getGrantType();
body = JsonUtils.toJsonString(loginBody);
}else{
SmsLoginBody loginBody = new SmsLoginBody();
loginBody.setPhonenumber(appSmsLoginBody.getPhonenumber());
loginBody.setSmsCode(appSmsLoginBody.getSmsCode());
loginBody.setTenantId(appSmsLoginBody.getTenantId());
loginBody.setClientId("ca839698e245d60aa2f0e59bd52b34f8");
loginBody.setGrantType("appSms");
clientId = loginBody.getClientId();
grantType = loginBody.getGrantType();
body = JsonUtils.toJsonString(loginBody);
}
SysClientVo client = clientService.queryByClientId(clientId);
// 查询不到 client 或 client 内不包含 grantType
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
@ -84,9 +124,9 @@ public class AppAuthController {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 校验租户
loginService.checkTenant(loginBody.getTenantId());
// loginService.checkTenant(loginBody.getTenantId());
loginService.checkTenant(tenantId);
// 登录
String body = JsonUtils.toJsonString(loginBody);
LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
return R.ok(loginVo);
}
@ -95,27 +135,74 @@ public class AppAuthController {
/**
* 用户注销
*/
@DeleteMapping("/cancelAccount")
@PostMapping("/cancelAccount")
public R<Void> cancelAccount() {
loginService.cancelAccount();
return R.ok("用户注销成功");
}
/**
* 退出登录
*/
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
/**
* 用户注册
*/
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody AppSmsRegisterBody user) {
registerService.register(user);
public R<Void> register(@Validated @RequestBody AppRegisterBody body) {
registerService.register(body);
return R.ok();
}
/**
* 找回密码
*/
@PostMapping("/forgetPassword")
public R<Void> forgetPassword(@Validated @RequestBody AppRegisterBody body) {
registerService.forgetPassword(body);
return R.ok();
}
/**
* 用户注册短信验证码
*
* @param phonenumber 用户手机号
*/
@SaIgnore
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/registerSmsCode")
public R<Void> registerSmsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.REGISTER_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map);
if (!smsResponse.isSuccess()) {
return R.fail(smsResponse.getData().toString());
}
return R.ok();
}
/**
* 找回密码短信验证码
*
* @param phonenumber 用户手机号
*/
@SaIgnore
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/forgetPasswordSmsCode")
public R<Void> forgetPasswordSmsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.FORGET_PASSWORD_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map);
if (!smsResponse.isSuccess()) {
return R.fail(smsResponse.getData().toString());
}
return R.ok();
}
@ -172,4 +259,20 @@ public class AppAuthController {
SmsResponse smsResponse = smsBlend.sendMessage("18656573389", "123");
}
/**
* 获取app版本
* @return
*/
@GetMapping("/version")
public R<List<SystemVersionDto>> getAppVersion() {
String versionKey = GLOBAL_REDIS_KEY + "System:Version:Xinghan";
// 缓存告警消息到Redis
String versionJson = RedisUtils.getCacheObject(versionKey);
if (StringUtils.isBlank(versionJson)) { // hutool 工具,可用 StringUtils.isBlank 代替
return R.fail(versionJson);
}
List<SystemVersionDto> list = JSON.parseArray(versionJson, SystemVersionDto.class);
return R.ok(list);
}
}

View File

@ -6,6 +6,7 @@ import com.fuyuanshen.app.domain.vo.APPDeviceTypeVo;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.dto.AppDeviceBo;
@ -53,7 +54,8 @@ public class AppDeviceController extends BaseController {
*/
@DeleteMapping("/unBind")
public R<Void> unBind(Long id) {
return toAjax(appDeviceService.unBindDevice(id));
Long userId = AppLoginHelper.getUserId();
return toAjax(appDeviceService.unBindDevice(id,userId,0));
}
/**

View File

@ -5,6 +5,7 @@ import cn.hutool.core.util.RandomUtil;
import com.fuyuanshen.app.domain.bo.AppDeviceShareBo;
import com.fuyuanshen.app.domain.vo.AppDeviceShareDetailVo;
import com.fuyuanshen.app.domain.vo.AppDeviceShareVo;
import com.fuyuanshen.app.service.AppDeviceShareService;
import com.fuyuanshen.app.service.IAppDeviceShareService;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.domain.R;
@ -45,7 +46,7 @@ public class AppDeviceShareController extends BaseController {
private final IAppDeviceShareService deviceShareService;
private final DeviceShareService appDeviceShareService;
private final AppDeviceShareService appDeviceShareService;
/**
* 分享管理列表

View File

@ -0,0 +1,55 @@
package com.fuyuanshen.app.controller;
import com.fuyuanshen.app.model.AppRegisterBody;
import com.fuyuanshen.app.model.AppUpdatePasswordBody;
import com.fuyuanshen.app.service.AppLoginService;
import com.fuyuanshen.app.service.AppRegisterService;
import com.fuyuanshen.common.core.domain.R;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* APP我的
*
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/userCenter")
public class AppUserCenterController {
private final AppLoginService loginService;
private final AppRegisterService registerService;
/**
* 用户注销
*/
@PostMapping("/cancelAccount")
public R<Void> cancelAccount() {
loginService.cancelAccount();
return R.ok("用户注销成功");
}
/**
* 修改密码
*/
@PostMapping("/updatePassword")
public R<Void> updatePassword(@Validated @RequestBody AppUpdatePasswordBody body) {
registerService.updatePassword(body);
return R.ok();
}
/**
* 退出登录
*/
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
}

View File

@ -1,88 +0,0 @@
package com.fuyuanshen.app.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.RandomUtil;
import com.fuyuanshen.app.domain.dto.APPForgotPasswordDTO;
import com.fuyuanshen.app.domain.dto.APPForgotPasswordSmsDTO;
import com.fuyuanshen.app.domain.dto.APPUpdateUserDTO;
import com.fuyuanshen.app.domain.vo.APPUserInfoVo;
import com.fuyuanshen.app.service.IAppUserService;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.ratelimiter.annotation.RateLimiter;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.web.core.BaseController;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.time.Duration;
import java.util.LinkedHashMap;
/**
* APP 用户管理
* @date 2025-06-27
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/user")
public class AppUserController extends BaseController {
private final IAppUserService appUserService;
/**
* 个人中心
*/
@GetMapping("/getUserInfo")
public R<APPUserInfoVo> getUserInfo() {
return R.ok(appUserService.getUserInfo());
}
/**
* 修改个人信息
*/
@PostMapping("/updateUser")
public R<Void> updateUser(@Validated @ModelAttribute APPUpdateUserDTO bo) {
return toAjax(appUserService.updateUser(bo));
}
/**
* 忘记密码
*/
@SaIgnore
@PostMapping("/forgotPassword")
public R<Void> forgotPassword(@RequestBody APPForgotPasswordDTO bo) {
return toAjax(appUserService.forgotPassword(bo));
}
/**
* 发送忘记密码短信
*
* @param phonenumber 用户手机号
*/
@SaIgnore
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/sendForgotPasswordSms")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.APP_FORGOT_PASSWORD_SMS_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map);
if (!smsResponse.isSuccess()) {
return R.fail(smsResponse.getData().toString());
}
return R.ok();
}
}

View File

@ -0,0 +1,54 @@
package com.fuyuanshen.app.controller;
import com.fuyuanshen.app.service.AudioProcessService;
import com.fuyuanshen.app.service.VideoProcessService;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
import com.fuyuanshen.common.web.core.BaseController;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* APP 视频处理控制器
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/video")
public class AppVideoController extends BaseController {
private final VideoProcessService videoProcessService;
private final AudioProcessService audioProcessService;
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!")
public R<List<String>> uploadVideo(@RequestParam("file") MultipartFile file) {
return R.ok(videoProcessService.processVideo(file));
}
/**
* 上传音频文件并转码
*/
@PostMapping(value = "/audio", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!")
public R<List<String>> uploadAudio(@RequestParam("file") MultipartFile file) {
return R.ok(audioProcessService.processAudio(file));
}
/**
* 文字转音频TTS服务
*/
@GetMapping("/audioTTS")
@RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!")
public R<List<String>> uploadAudioTTS(@RequestParam String text) throws IOException {
return R.ok(audioProcessService.generateStandardPcmData(text));
}
}

View File

@ -5,6 +5,7 @@ import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
@ -17,6 +18,8 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* BJQ6170设备控制类
*/
@ -82,6 +85,8 @@ public class AppDeviceBJQController extends BaseController {
return R.ok();
}
/**
* 灯光模式
* 0关灯1强光模式2弱光模式, 3爆闪模式, 4泛光模式

View File

@ -0,0 +1,119 @@
package com.fuyuanshen.app.controller.device;
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.web.service.device.DeviceBJQBizService;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* HBY210设备控制类
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/hby/device")
public class AppDeviceHBYController extends BaseController {
private final DeviceBJQBizService appDeviceService;
/**
* 获取设备详细信息
*
* @param id 主键
*/
@GetMapping("/{id}")
public R<AppDeviceDetailVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(appDeviceService.getInfo(id));
}
/**
* 人员信息登记
*/
@PostMapping(value = "/registerPersonInfo")
// @FunctionAccessAnnotation("registerPersonInfo")
public R<Void> registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) {
return toAjax(appDeviceService.registerPersonInfo(bo));
}
/**
* 发送信息
*/
@PostMapping(value = "/sendMessage")
@FunctionAccessBatcAnnotation(value = "sendMessage", timeOut = 30, batchMaxTimeOut = 40)
public R<Void> sendMessage(@RequestBody AppDeviceSendMsgBo bo) {
return toAjax(appDeviceService.sendMessage(bo));
}
/**
* 发送报警信息
*/
@PostMapping(value = "/sendAlarmMessage")
@FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10)
public R<Void> sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) {
return toAjax(appDeviceService.sendAlarmMessage(bo));
}
/**
* 上传设备logo图片
*/
@PostMapping("/uploadLogo")
@FunctionAccessAnnotation("uploadLogo")
public R<Void> upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) {
MultipartFile file = bo.getFile();
if(file.getSize()>1024*1024*2){
return R.warn("图片不能大于2M");
}
appDeviceService.uploadDeviceLogo(bo);
return R.ok();
}
/**
* 灯光模式
* 0关灯1强光模式2弱光模式, 3爆闪模式, 4泛光模式
*/
// @FunctionAccessAnnotation("lightModeSettings")
@PostMapping("/lightModeSettings")
public R<Void> lightModeSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
appDeviceService.lightModeSettings(params);
return R.ok();
}
/**
* 灯光亮度设置
*
*/
// @FunctionAccessAnnotation("lightBrightnessSettings")
@PostMapping("/lightBrightnessSettings")
public R<Void> lightBrightnessSettings(@RequestBody DeviceInstructDto params) {
appDeviceService.lightBrightnessSettings(params);
return R.ok();
}
/**
* 激光模式设置
*
*/
@PostMapping("/laserModeSettings")
// @FunctionAccessAnnotation("laserModeSettings")
public R<Void> laserModeSettings(@RequestBody DeviceInstructDto params) {
appDeviceService.laserModeSettings(params);
return R.ok();
}
}

View File

@ -5,15 +5,28 @@ import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.customer.mapper.CustomerMapper;
import com.fuyuanshen.equipment.domain.DeviceType;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.equipment.domain.form.DeviceForm;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.equipment.service.DeviceTypeService;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
import com.fuyuanshen.web.service.device.DeviceBJQBizService;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* HBY670设备控制类
*/
@ -24,18 +37,31 @@ import org.springframework.web.multipart.MultipartFile;
public class AppDeviceXinghanController extends BaseController {
private final DeviceXinghanBizService appDeviceService;
private final DeviceService deviceService;
/**
* 人员信息登记
*/
@Log(title = "xinghan指令-人员信息登记")
@PostMapping(value = "/registerPersonInfo")
// @FunctionAccessAnnotation("registerPersonInfo")
public R<Void> registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) {
return toAjax(appDeviceService.registerPersonInfo(bo));
}
/**
* 发送紧急通知
*/
@Log(title = "xinghan指令-发送紧急通知")
@PostMapping(value = "/sendAlarmMessage")
@FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10)
public R<Void> sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) {
return toAjax(appDeviceService.sendAlarmMessage(bo));
}
/**
* 上传设备logo图片
*/
@Log(title = "xinghan指令-上传设备logo图片")
@PostMapping("/uploadLogo")
@FunctionAccessAnnotation("uploadLogo")
public R<Void> upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) {
@ -53,8 +79,9 @@ public class AppDeviceXinghanController extends BaseController {
* 静电预警档位
* 3,2,1,0,分别表示高档/中档/低挡/关闭
*/
@Log(title = "xinghan指令-静电预警档位")
@PostMapping("/DetectGradeSettings")
public R<Void> DetectGradeSettings(@RequestBody DeviceInstructDto params) {
public R<Void> DetectGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
appDeviceService.upDetectGradeSettings(params);
return R.ok();
@ -64,19 +91,21 @@ public class AppDeviceXinghanController extends BaseController {
* 照明档位
* 照明档位2,1,0,分别表示弱光/强光/关闭
*/
@Log(title = "xinghan指令-照明档位")
@PostMapping("/LightGradeSettings")
public R<Void> LightGradeSettings(@RequestBody DeviceInstructDto params) {
public R<Void> LightGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
appDeviceService.upLightGradeSettings(params);
return R.ok();
}
/**
* SOS档位
* SOS档位s
* SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/
@Log(title = "xinghan指令-SOS档位s")
@PostMapping("/SOSGradeSettings")
public R<Void> SOSGradeSettings(@RequestBody DeviceInstructDto params) {
public R<Void> SOSGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
appDeviceService.upSOSGradeSettings(params);
return R.ok();
@ -86,11 +115,31 @@ public class AppDeviceXinghanController extends BaseController {
* 静止报警状态
* 静止报警状态0-未静止报警1-正在静止报警。
*/
@Log(title = "xinghan指令-静止报警状态")
@PostMapping("/ShakeBitSettings")
public R<Void> ShakeBitSettings(@RequestBody DeviceInstructDto params) {
public R<Void> ShakeBitSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
appDeviceService.upShakeBitSettings(params);
return R.ok();
}
@GetMapping(value = "/typeAll")
@Operation(summary = "查询所有设备类型")
public R<List<DeviceType>> queryDeviceTypes() {
List<DeviceType> deviceTypes = appDeviceService.queryDeviceTypes();
return R.ok(deviceTypes);
}
// @Log("新增设备")
@Operation(summary = "新增设备")
@PostMapping(value = "/add")
public R<Void> addDevice(@RequestBody DeviceForm deviceForm) {
try {
appDeviceService.addDevice(deviceForm);
} catch (Exception e) {
return R.fail(e.getMessage());
}
return R.ok();
}
}

View File

@ -3,6 +3,8 @@ package com.fuyuanshen.app.domain.dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Data
public class AppDeviceLogoUploadDto {
@ -14,5 +16,13 @@ public class AppDeviceLogoUploadDto {
*/
private MultipartFile file;
// /**
// * 文件
// */
// private List<MultipartFile> files;
private List<Long> deviceIds;
private Integer chunkSize;
}

View File

@ -18,4 +18,9 @@ public class AppFileDto {
*/
private MultipartFile[] files;
/**
* 多设备id
*/
private Long[] deviceIds;
}

View File

@ -0,0 +1,30 @@
package com.fuyuanshen.app.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class AppRegisterBody {
/**
* 手机号
*/
@NotBlank(message = "{user.phonenumber.not.blank}")
private String phonenumber;
/**
* 短信code
*/
private String smsCode;
/**
* 租户ID
*/
@NotBlank(message = "租户ID不能为空")
private String tenantId;
/**
* 登录密码
*/
private String password;
}

View File

@ -14,7 +14,6 @@ public class AppSmsLoginBody {
/**
* 短信code
*/
@NotBlank(message = "{sms.code.not.blank}")
private String smsCode;
/**
@ -23,4 +22,14 @@ public class AppSmsLoginBody {
@NotBlank(message = "租户ID不能为空")
private String tenantId;
/**
* 登录方式 1:手机验证码登录 2:密码登录
*/
@NotBlank(message = "登录方式不能为空")
private String loginType;
/**
* 登录密码
*/
private String loginPassword;
}

View File

@ -0,0 +1,12 @@
package com.fuyuanshen.app.model;
import lombok.Data;
@Data
public class AppUpdatePasswordBody {
/**
* 登录密码
*/
private String password;
}

View File

@ -0,0 +1,263 @@
package com.fuyuanshen.app.service;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fuyuanshen.app.domain.AppDeviceShare;
import com.fuyuanshen.app.domain.AppPersonnelInfo;
import com.fuyuanshen.app.domain.bo.AppDeviceShareBo;
import com.fuyuanshen.app.domain.vo.AppDeviceShareDetailVo;
import com.fuyuanshen.app.domain.vo.AppDeviceShareVo;
import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
import com.fuyuanshen.app.mapper.AppDeviceShareMapper;
import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceType;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
@RequiredArgsConstructor
@Slf4j
@Service
public class AppDeviceShareService {
private final AppDeviceShareMapper appDeviceShareMapper;
private final DeviceMapper deviceMapper;
private final DeviceTypeMapper deviceTypeMapper;
private final AppPersonnelInfoMapper appPersonnelInfoMapper;
public TableDataInfo<AppDeviceShareVo> queryPageList(AppDeviceShareBo bo, PageQuery pageQuery) {
Long userId = AppLoginHelper.getUserId();
bo.setCreateBy(userId);
Page<AppDeviceShareVo> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
Page<AppDeviceShareVo> result = appDeviceShareMapper.selectAppDeviceShareList(bo, page);
List<AppDeviceShareVo> records = result.getRecords();
records.forEach(AppDeviceShareService::buildDeviceStatus);
return TableDataInfo.build(result);
}
public TableDataInfo<AppDeviceShareVo> queryWebPageList(AppDeviceShareBo bo, PageQuery pageQuery) {
// Long userId = AppLoginHelper.getUserId();
// bo.setCreateBy(userId);
Page<AppDeviceShareVo> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
Page<AppDeviceShareVo> result = appDeviceShareMapper.selectWebDeviceShareList(bo, page);
List<AppDeviceShareVo> records = result.getRecords();
records.forEach(AppDeviceShareService::buildDeviceStatus);
return TableDataInfo.build(result);
}
private static void buildDeviceStatus(AppDeviceShareVo item) {
// 设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
if("1".equals(onlineStatus)){
item.setOnlineStatus(1);
}else if("2".equals(onlineStatus)){
item.setOnlineStatus(2);
}else{
item.setOnlineStatus(0);
}
String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DEVICE_STATUS_KEY_PREFIX);
// 获取电量
if (StringUtils.isNotBlank(deviceStatus)) {
JSONObject jsonObject = JSONObject.parseObject(deviceStatus);
item.setBattery(jsonObject.getString("batteryPercentage"));
} else {
item.setBattery("0");
}
String location = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DEVICE_LOCATION_KEY_PREFIX);
if (StringUtils.isNotBlank(location)) {
JSONObject jsonObject = JSONObject.parseObject(location);
item.setLatitude(jsonObject.getString("latitude"));
item.setLongitude(jsonObject.getString("longitude"));
}
String alarmStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX);
if (StringUtils.isNotBlank(alarmStatus)) {
item.setAlarmStatus(alarmStatus);
}
}
public AppDeviceShareDetailVo getInfo(Long id) {
LambdaQueryWrapper<AppDeviceShare> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AppDeviceShare::getId, id);
List<AppDeviceShareVo> appDeviceShareVos = appDeviceShareMapper.selectVoList(queryWrapper);
if (appDeviceShareVos == null || appDeviceShareVos.isEmpty()) {
return null;
}
AppDeviceShareVo shareVo = appDeviceShareVos.get(0);
AppDeviceShareDetailVo shareDetailVo = new AppDeviceShareDetailVo();
shareDetailVo.setId(shareVo.getId());
shareDetailVo.setDeviceId(shareVo.getDeviceId());
shareDetailVo.setPhonenumber(shareVo.getPhonenumber());
shareDetailVo.setPermission(shareVo.getPermission());
Device device = deviceMapper.selectById(shareVo.getDeviceId());
shareDetailVo.setDeviceName(device.getDeviceName());
shareDetailVo.setDeviceImei(device.getDeviceImei());
shareDetailVo.setDeviceMac(device.getDeviceMac());
DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType());
if (deviceType != null) {
shareDetailVo.setCommunicationMode(Integer.valueOf(deviceType.getCommunicationMode()));
}
shareDetailVo.setDevicePic(device.getDevicePic());
shareDetailVo.setTypeName(deviceType.getTypeName());
shareDetailVo.setBluetoothName(device.getBluetoothName());
shareDetailVo.setDeviceStatus(device.getDeviceStatus());
shareDetailVo.setSendMsg(device.getSendMsg());
LambdaQueryWrapper<AppPersonnelInfo> qw = new LambdaQueryWrapper<>();
qw.eq(AppPersonnelInfo::getDeviceId, device.getId());
List<AppPersonnelInfoVo> appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw);
if (appPersonnelInfoVos != null && !appPersonnelInfoVos.isEmpty()) {
shareDetailVo.setPersonnelInfo(appPersonnelInfoVos.get(0));
}
// 设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
if (StringUtils.isNotBlank(onlineStatus)) {
shareDetailVo.setOnlineStatus(1);
} else {
shareDetailVo.setOnlineStatus(0);
}
String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX);
// 获取电量
if (StringUtils.isNotBlank(deviceStatus)) {
JSONObject jsonObject = JSONObject.parseObject(deviceStatus);
shareDetailVo.setMainLightMode(jsonObject.getString("mainLightMode"));
shareDetailVo.setLaserLightMode(jsonObject.getString("laserLightMode"));
shareDetailVo.setBatteryPercentage(jsonObject.getString("batteryPercentage"));
shareDetailVo.setChargeState(jsonObject.getString("chargeState"));
shareDetailVo.setBatteryRemainingTime(jsonObject.getString("batteryRemainingTime"));
} else {
shareDetailVo.setBatteryPercentage("0");
}
// 获取经度纬度
String locationKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX;
String locationInfo = RedisUtils.getCacheObject(locationKey);
if (StringUtils.isNotBlank(locationInfo)) {
JSONObject jsonObject = JSONObject.parseObject(locationInfo);
shareDetailVo.setLongitude(jsonObject.get("longitude").toString());
shareDetailVo.setLatitude(jsonObject.get("latitude").toString());
shareDetailVo.setAddress((String) jsonObject.get("address"));
}
String alarmStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX);
if (StringUtils.isNotBlank(alarmStatus)) {
shareDetailVo.setAlarmStatus(alarmStatus);
}
String lightBrightness = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_LIGHT_BRIGHTNESS_KEY_PREFIX);
if (StringUtils.isNotBlank(lightBrightness)) {
shareDetailVo.setLightBrightness(lightBrightness);
}
return shareDetailVo;
}
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.DEVICE_SHARE_CODES_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
throw new ServiceException("验证码失效");
}
return code.equals(smsCode);
}
public int deviceShare(AppDeviceShareBo bo) {
boolean flag = validateSmsCode(AppLoginHelper.getTenantId(), bo.getPhonenumber(), bo.getSmsCode());
if (!flag) {
throw new ServiceException("验证码错误");
}
Device device = deviceMapper.selectById(bo.getDeviceId());
if (device == null) {
throw new ServiceException("设备不存在");
}
Long userId = AppLoginHelper.getUserId();
LambdaQueryWrapper<AppDeviceShare> lqw = new LambdaQueryWrapper<>();
lqw.eq(AppDeviceShare::getDeviceId, bo.getDeviceId());
lqw.eq(AppDeviceShare::getPhonenumber, bo.getPhonenumber());
Long count = appDeviceShareMapper.selectCount(lqw);
if (count > 0) {
UpdateWrapper<AppDeviceShare> uw = new UpdateWrapper<>();
uw.eq("device_id", bo.getDeviceId());
uw.eq("phonenumber", bo.getPhonenumber());
uw.set("permission", bo.getPermission());
uw.set("update_by", userId);
uw.set("update_time", new Date());
return appDeviceShareMapper.update(uw);
} else {
AppDeviceShare appDeviceShare = new AppDeviceShare();
appDeviceShare.setDeviceId(bo.getDeviceId());
appDeviceShare.setPhonenumber(bo.getPhonenumber());
appDeviceShare.setPermission(bo.getPermission());
appDeviceShare.setCreateBy(userId);
return appDeviceShareMapper.insert(appDeviceShare);
}
}
public int remove(Long[] ids) {
return appDeviceShareMapper.deleteByIds(Arrays.asList(ids));
}
public TableDataInfo<AppDeviceShareVo> otherDeviceShareList(AppDeviceShareBo bo, PageQuery pageQuery) {
String username = AppLoginHelper.getUsername();
bo.setPhonenumber(username);
Page<AppDeviceShareVo> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
IPage<AppDeviceShareVo> result = appDeviceShareMapper.otherDeviceShareList(bo, page);
List<AppDeviceShareVo> records = result.getRecords();
records.forEach(AppDeviceShareService::buildDeviceStatus);
return TableDataInfo.build(result);
}
/**
* 查询设备分享列表(web)
*
* @param bo
* @param pageQuery
* @return
*/
public TableDataInfo<AppDeviceShareVo> queryWebList(AppDeviceShareBo bo, PageQuery pageQuery) {
Page<AppDeviceShareVo> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
Page<AppDeviceShareVo> result = appDeviceShareMapper.selectWebDeviceShareList(bo, page);
List<AppDeviceShareVo> records = result.getRecords();
records.forEach(AppDeviceShareService::buildDeviceStatus);
return TableDataInfo.build(result);
}
}

View File

@ -32,7 +32,7 @@ public class AppFileService {
private final IAppBusinessFileService appBusinessFileService;
public List<AppFileVo> list(AppBusinessFileBo bo) {
bo.setCreateBy(AppLoginHelper.getUserId());
// bo.setCreateBy(AppLoginHelper.getUserId());
return appBusinessFileService.queryAppFileList(bo);
}
@ -68,7 +68,7 @@ public class AppFileService {
public Boolean delete(Long[] ids) {
AppBusinessFileBo bo = new AppBusinessFileBo();
bo.setCreateBy(AppLoginHelper.getUserId());
// bo.setCreateBy(AppLoginHelper.getUserId());
bo.setIds(List.of(ids));
List<AppBusinessFileVo> appBusinessFileVos = appBusinessFileService.queryList(bo);
List<Long> fileIds = appBusinessFileVos.stream().map(AppBusinessFileVo::getFileId).toList();

View File

@ -0,0 +1,156 @@
package com.fuyuanshen.app.service;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fuyuanshen.app.domain.AppUser;
import com.fuyuanshen.app.mapper.AppUserMapper;
import com.fuyuanshen.app.model.AppRegisterBody;
import com.fuyuanshen.app.model.AppUpdatePasswordBody;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.model.RegisterBody;
import com.fuyuanshen.common.core.enums.UserType;
import com.fuyuanshen.common.core.exception.user.CaptchaException;
import com.fuyuanshen.common.core.exception.user.CaptchaExpireException;
import com.fuyuanshen.common.core.exception.user.UserException;
import com.fuyuanshen.common.core.utils.MessageUtils;
import com.fuyuanshen.common.core.utils.ServletUtils;
import com.fuyuanshen.common.core.utils.SpringUtils;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.log.event.LogininforEvent;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.common.tenant.helper.TenantHelper;
import com.fuyuanshen.common.web.config.properties.CaptchaProperties;
import com.fuyuanshen.system.domain.SysUser;
import com.fuyuanshen.system.domain.bo.SysUserBo;
import com.fuyuanshen.system.mapper.SysUserMapper;
import com.fuyuanshen.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* 注册校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class AppRegisterService {
private final IAppUserService userService;
private final AppUserMapper userMapper;
/**
* 注册
*/
public void register(AppRegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getPhonenumber();
String password = registerBody.getPassword();
boolean codeValidate = validateRegisterSmsCode(tenantId, username, registerBody.getSmsCode());
if (!codeValidate) {
throw new CaptchaException();
}
// 校验用户类型是否存在
String userType = UserType.APP_USER.getUserType();
boolean exist = TenantHelper.dynamic(tenantId, () -> {
return userMapper.exists(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getUserName, username));
});
if (exist) {
throw new UserException("user.register.save.error", username);
}
AppUser appUser = new AppUser();
appUser.setPhonenumber(username);
appUser.setUserName(username);
appUser.setStatus("0");
appUser.setLoginDate(new Date());
appUser.setLoginIp(ServletUtils.getClientIP());
appUser.setTenantId(tenantId);
appUser.setPassword(password);
appUser.setUserType(userType);
userMapper.insert(appUser);
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
* 注册校验短信验证码
*/
private boolean validateRegisterSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.REGISTER_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
/**
* 忘记密码校验验证码
*/
private boolean validateForgetPasswordCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.FORGET_PASSWORD_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
public void forgetPassword(AppRegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getPhonenumber();
String password = registerBody.getPassword();
boolean codeValidate = validateForgetPasswordCode(tenantId, username, registerBody.getSmsCode());
if (!codeValidate) {
throw new CaptchaException();
}
boolean exist = TenantHelper.dynamic(tenantId, () -> {
return userMapper.exists(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getUserName, username));
});
if (!exist) {
throw new UserException("用户不存在");
}
UpdateWrapper<AppUser> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("user_name", username)
.set("password", password);
userMapper.update(updateWrapper);
}
public void updatePassword(AppUpdatePasswordBody body) {
String username = AppLoginHelper.getUsername();
String password = body.getPassword();
UpdateWrapper<AppUser> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("user_name", username)
.set("password", password);
userMapper.update(updateWrapper);
}
}

View File

@ -0,0 +1,174 @@
package com.fuyuanshen.app.service;
import com.fuyuanshen.equipment.utils.AlibabaTTSUtil;
import com.fuyuanshen.equipment.utils.AudioProcessUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
/**
* 音频处理服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AudioProcessService {
// 配置参数
private static final int MAX_AUDIO_SIZE = 5 * 1024 * 1024; // 5MB
private static final List<String> SUPPORTED_FORMATS = Arrays.asList(
".wav", ".mp3", ".aac", ".flac", ".m4a", ".ogg"
);
private final AudioProcessUtil audioProcessUtil;
private final AlibabaTTSUtil alibabaTTSUtil;
/**
* 处理上传的音频文件
*/
public List<String> processAudio(MultipartFile file) {
// 1. 参数校验
validateAudioFile(file);
File tempFile = null;
try {
// 2. 创建临时文件
tempFile = createTempAudioFile(file);
// 3. 转码为标准PCM-WAV格式
byte[] pcmData = audioProcessUtil.convertToStandardWav(tempFile);
log.info("音频处理成功,输出数据大小: {} bytes", pcmData.length);
// 获取音频信息
// String audioInfo = audioProcessUtil.getAudioInfo(pcmData);
// log.info("音频处理成功,音频信息: {}", audioInfo);
//
// // 保存测试文件(用于验证)
// String savedPath = audioProcessUtil.saveWavToFile(pcmData, "test_output.wav");
// if (savedPath != null) {
// log.info("测试文件已保存: {}", savedPath);
// }
// 将byte[]转换为16进制字符串列表
List<String> hexList = audioProcessUtil.bytesToHexList(pcmData);
log.info("音频处理完成,原始数据大小: {} bytes, 16进制数据长度: {}",
pcmData.length, hexList.size());
return hexList;
} catch (Exception e) {
log.error("音频处理失败", e);
throw new RuntimeException("音频处理失败", e);
} finally {
// 4. 清理临时文件
deleteTempFile(tempFile);
}
}
/**
* 生成标准PCM数据单声道16K采样率16bit深度包含44字节WAV头
* 数据总大小不超过2MB如果超过将抛出异常
* @param text 要转换的文本内容
* @return 标准PCM数据字节数组WAV格式
* @throws IOException 处理失败时抛出
* @throws IllegalArgumentException 如果生成的数据超过2MB
*/
public List<String> generateStandardPcmData(String text) throws IOException {
// 参数校验
if (text == null || text.trim().isEmpty()) {
throw new IllegalArgumentException("文本内容不能为空");
}
if (text.length() > 100) {
throw new IllegalArgumentException("文本长度超过限制最大100字符");
}
log.info("输入文本长度: {}", text.length());
try {
byte[] rawPcmData = alibabaTTSUtil.generateStandardPcmData(text);
// 使用AudioProcessUtil转换成带头44字节 PCM
byte[] pcmData = audioProcessUtil.rawPcmToStandardWav(rawPcmData);
// String savedPath = audioProcessUtil.saveWavToFile(pcmData, "test_output.wav");
// if (savedPath != null) {
// log.info("测试文件已保存: {}", savedPath);
// }
// 将byte[]转换为16进制字符串列表
List<String> hexList = audioProcessUtil.bytesToHexList(pcmData);
log.info("generateStandardPcmData音频处理完成原始数据大小: {} bytes, 16进制数据长度: {}",
pcmData.length, hexList.size());
return hexList;
} finally {
// 4. 清理临时文件
}
}
/**
* 验证音频文件
*/
private void validateAudioFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
if (!isAudioFile(file.getOriginalFilename())) {
throw new IllegalArgumentException("只允许上传音频文件");
}
if (file.getSize() > MAX_AUDIO_SIZE) {
throw new IllegalArgumentException("音频大小不能超过5MB");
}
}
/**
* 判断是否是支持的音频格式
*/
private boolean isAudioFile(String filename) {
if (filename == null || filename.lastIndexOf('.') == -1) {
return false;
}
String ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
return SUPPORTED_FORMATS.contains(ext);
}
/**
* 创建临时音频文件
*/
private File createTempAudioFile(MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String extension = "";
if (originalFilename != null && originalFilename.contains(".")) {
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
File tempFile = File.createTempFile("audio-", extension);
file.transferTo(tempFile);
log.debug("创建临时音频文件: {}", tempFile.getAbsolutePath());
return tempFile;
}
/**
* 删除临时文件
*/
private void deleteTempFile(File file) {
if (file != null && file.exists()) {
if (file.delete()) {
log.debug("删除临时文件成功: {}", file.getAbsolutePath());
} else {
log.warn("无法删除临时文件: {}", file.getAbsolutePath());
}
}
}
}

View File

@ -0,0 +1,84 @@
package com.fuyuanshen.app.service;
import com.fuyuanshen.web.util.VideoProcessUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.Arrays;
import java.util.List;
/**
* 视频处理服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class VideoProcessService {
// 配置参数
private static final int MAX_VIDEO_SIZE = 10 * 1024 * 1024;
private static final List<String> SUPPORTED_FORMATS = Arrays.asList(".mp4", ".avi", ".mov", ".mkv");
private static final int FRAME_RATE = 15;
private static final int DURATION = 2;
private static final int WIDTH = 160;
private static final int HEIGHT = 80;
private final VideoProcessUtil videoProcessUtil;
public List<String> processVideo(MultipartFile file) {
// 1. 参数校验
validateVideoFile(file);
File tempFile = null;
try {
// 2. 创建临时文件
tempFile = videoProcessUtil.createTempVideoFile(file);
// 3. 处理视频并提取帧数据
List<String> hexList = videoProcessUtil.processVideoToHex(
tempFile, FRAME_RATE, DURATION, WIDTH, HEIGHT
);
log.info("视频处理成功生成Hex数据长度: {}", hexList.size());
return hexList;
} catch (Exception e) {
log.error("视频处理失败", e);
throw new RuntimeException("视频处理失败", e);
} finally {
// 4. 清理临时文件
videoProcessUtil.deleteTempFile(tempFile);
}
}
/**
* 验证视频文件
*/
private void validateVideoFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
if (!isVideoFile(file.getOriginalFilename())) {
throw new IllegalArgumentException("只允许上传视频文件");
}
if (file.getSize() > MAX_VIDEO_SIZE) {
throw new IllegalArgumentException("视频大小不能超过10MB");
}
}
/**
* 判断是否是支持的视频格式
*/
private boolean isVideoFile(String filename) {
if (filename == null || filename.lastIndexOf('.') == -1) {
return false;
}
String ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
return SUPPORTED_FORMATS.contains(ext);
}
}

View File

@ -39,8 +39,8 @@ public class MqttXinghanJson {
/**
* 第七键值对, 静止报警状态0-未静止报警1-正在静止报警。
*/
@JsonProperty("staShakeBit")
public Integer sta_ShakeBit;
@JsonProperty("sta_ShakeBit")
public Integer staShakeBit;
/**
* 第八键值对, 4G信号强度0-32数值越大信号越强。
*/
@ -61,5 +61,14 @@ public class MqttXinghanJson {
*/
@JsonProperty("sta_latitude")
public String stalatitude;
/**
* 第十二键值对系统现状0关机1仅充电2开机未充电,3开机且充电
*/
@JsonProperty("sta_system")
public String stasystem;
/**
* 电量百分比(适配控制列表显示)
*/
public String batteryPercentage;
}

View File

@ -3,6 +3,7 @@ package com.fuyuanshen.global.mqtt.config;
import cn.hutool.core.lang.UUID;
import com.fuyuanshen.global.mqtt.receiver.ReceiverMessageHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -55,4 +56,10 @@ public class MqttInboundConfiguration {
public MessageHandler messageHandler(){
return receiverMessageHandler;
}
// @Bean
// @ServiceActivator(inputChannel = "messageInboundChannel") // 确保通道名称正确
// public MessageHandler deviceAlarmMessageHandler() {
// return new DeviceAlrmMessageHandler();
// }
}

View File

@ -47,4 +47,9 @@ public class DeviceRedisKeyConstants {
* 告警
*/
public static final String DEVICE_ALARM_KEY_PREFIX = ":alarm";
/**
* 告警信息
*/
public static final String DEVICE_ALARM_MESSAGE_KEY_PREFIX = ":alarmMessage";
}

View File

@ -1,23 +1,47 @@
package com.fuyuanshen.global.mqtt.listener;
import cn.hutool.core.thread.ThreadUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.executor.RedissonLockExecutor;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_TIMEOUT_KEY;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX;
@Component
@Slf4j
public class RedisKeyExpirationListener implements MessageListener {
@Autowired
@Qualifier("threadPoolTaskExecutor")
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Autowired
private DeviceMapper deviceMapper;
@Autowired
private LockTemplate lockTemplate;
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = new String(message.getBody());
@ -26,8 +50,51 @@ public class RedisKeyExpirationListener implements MessageListener {
String element = expiredKey.substring(FUNCTION_ACCESS_KEY.length());
handleFunctionAccessExpired(element);
}
}
if(expiredKey.endsWith(DEVICE_ONLINE_STATUS_KEY_PREFIX)) {
threadPoolTaskExecutor.execute(() -> {
log.info("设备离线:{}", expiredKey);
String element = expiredKey.substring(GlobalConstants.GLOBAL_REDIS_KEY.length() + DEVICE_KEY_PREFIX.length(), expiredKey.length() - DEVICE_ONLINE_STATUS_KEY_PREFIX.length());
// 构造设备锁键
String deviceLockKey = GlobalConstants.GLOBAL_REDIS_KEY + ":device_lock:" + element;
// 尝试获取Redis锁
LockInfo lockInfo = lockTemplate.lock(deviceLockKey, 30000L, 5000L, RedissonLockExecutor.class); // 30秒过期
if (lockInfo != null) {
try {
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
// String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey);
// if(StringUtils.isBlank(deviceOnlineStatusRedis)){
// UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
// deviceUpdateWrapper.eq("device_imei", element);
// deviceUpdateWrapper.set("online_status", 0);
// deviceMapper.update(deviceUpdateWrapper);
// }else{
// RedisUtils.deleteObject(deviceOnlineStatusRedisKey);
// }
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("device_imei", element);
deviceUpdateWrapper.set("online_status", 0);
deviceMapper.update(deviceUpdateWrapper);
String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey);
if(StringUtils.isNotBlank(deviceOnlineStatusRedis)){
RedisUtils.deleteObject(deviceOnlineStatusRedisKey);
}
} finally {
//释放锁
lockTemplate.releaseLock(lockInfo);
}
} else {
log.warn("无法获取设备锁,跳过设备离线处理: {}", element);
}
});
}
}
/**
* 访问key过期事件
* @param element 批次ID

View File

@ -48,17 +48,18 @@ public class ReceiverMessageHandler implements MessageHandler {
}
String[] subStr = receivedTopic.split("/");
String deviceImei = subStr[1];
if(StringUtils.isNotBlank(deviceImei)){
//在线状态
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62));
// String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY;
// String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY;
// RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24));
}
String state = payloadDict.getStr("state");
Object[] convertArr = ImageToCArrayConverter.convertByteStringToMixedObjectArray(state);
if(StringUtils.isNotBlank(deviceImei)){
String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY;
String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY;
RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofSeconds(900));
//在线状态
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(360));
}
if (convertArr.length > 0) {
Byte val1 = (Byte) convertArr[0];

View File

@ -1,8 +1,18 @@
package com.fuyuanshen.global.mqtt.rule.bjq;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSONObject;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.model.LoginUser;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.core.utils.date.DurationUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
@ -13,9 +23,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
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.*;
/**
@ -26,6 +38,10 @@ import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
@Slf4j
public class BjqAlarmRule implements MqttMessageRule {
private final IDeviceAlarmService deviceAlarmService;
private final DeviceService deviceService;
@Override
public String getCommandType() {
return LightingCommandTypeConstants.ALARM_MESSAGE;
@ -38,14 +54,26 @@ public class BjqAlarmRule implements MqttMessageRule {
Object[] convertArr = context.getConvertArr();
String convertValue = convertArr[1].toString();
if(StringUtils.isNotBlank(convertValue)){
if (StringUtils.isNotBlank(convertValue)) {
// 将设备状态信息存储到Redis中
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX;
// 存储到Redis
RedisUtils.setCacheObject(deviceRedisKey, convertValue);
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX;
String sendMessageIng = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + context.getDeviceImei() + ":messageSending";
if ("1".equals(convertValue)) {
RedisUtils.setCacheObject(sendMessageIng, "1", Duration.ofDays(1));
// 存储到Redis
RedisUtils.setCacheObject(deviceRedisKey, "1");
}else if ("0".equals(convertValue)){
RedisUtils.deleteObject(sendMessageIng);
RedisUtils.deleteObject(deviceRedisKey);
}
}
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
// 保存告警信息
String deviceImei = context.getDeviceImei();
// 设备告警状态 0:解除告警 1:报警产生
Byte state = (Byte) convertArr[1];
savaAlarm(deviceImei, state);
} catch (Exception e) {
log.error("处理告警命令时出错", e);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
@ -53,4 +81,55 @@ public class BjqAlarmRule implements MqttMessageRule {
}
public void savaAlarm(String deviceImei, Byte state) {
DeviceAlarmVo deviceAlarmVo = deviceAlarmService.queryLatestByDeviceImei(deviceImei);
DeviceAlarmBo deviceAlarmBo = new DeviceAlarmBo();
// 解除告警
if (state == 0) {
if (deviceAlarmVo != null) {
if (deviceAlarmVo.getFinishTime() == null) {
BeanUtil.copyProperties(deviceAlarmVo, deviceAlarmBo);
deviceAlarmBo.setFinishTime(new Date());
String durationBetween = DurationUtils.getDurationBetween(deviceAlarmVo.getStartTime(), deviceAlarmBo.getFinishTime());
deviceAlarmBo.setDurationTime(durationBetween);
// 0已处理1未处理
deviceAlarmBo.setTreatmentState(0);
// 告警状态0 解除告警, 1 告警中
deviceAlarmBo.setAlarmState(0);
deviceAlarmService.updateByBo(deviceAlarmBo);
}
}
}
// 报警产生
if (state == 1) {
if (deviceAlarmVo == null || deviceAlarmVo.getFinishTime() != null) {
Device device = deviceService.selectDeviceByImei(deviceImei);
deviceAlarmBo.setDeviceId(device.getId());
deviceAlarmBo.setDeviceImei(deviceImei);
// 0-强制报警1-撞击闯入2-自动报警3-电子围栏告警
deviceAlarmBo.setDeviceAction(0);
deviceAlarmBo.setStartTime(new Date());
// 0已处理1未处理
deviceAlarmBo.setTreatmentState(1);
// 告警状态0 解除告警, 1 告警中
deviceAlarmBo.setAlarmState(1);
// LoginUser loginUser = LoginHelper.getLoginUser();
// deviceAlarmBo.setCreateBy(loginUser.getUserId());
deviceAlarmBo.setTenantId(device.getTenantId());
String location = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX);
if (StringUtils.isNotBlank(location)) {
JSONObject jsonObject = JSONObject.parseObject(location);
deviceAlarmBo.setLocation(jsonObject.getString("address"));
}
deviceAlarmService.insertByBo(deviceAlarmBo);
}
}
}
}

View File

@ -5,6 +5,8 @@ import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.json.utils.JsonUtils;
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.LngLonUtil;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
@ -36,8 +38,9 @@ import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVIC
@RequiredArgsConstructor
@Slf4j
public class BjqLocationDataRule implements MqttMessageRule {
private final MqttGateway mqttGateway;
private final DeviceService deviceService;
@Override
@ -53,9 +56,15 @@ public class BjqLocationDataRule implements MqttMessageRule {
// Latitude, longitude
String latitude = convertArr[1].toString();
String longitude = convertArr[2].toString();
// 判断 latitude 和 longitude 是否都为 0
if ("0".equals(latitude) && "0".equals(longitude)) {
log.info("位置信息为0不存储到Redis: device={}, lat={}, lon={}", context.getDeviceImei(), latitude, longitude);
return;
}
// 异步发送经纬度到Redis
asyncSendLocationToRedisWithFuture(context.getDeviceImei(), latitude, longitude);
// 异步保存数据
asyncSaveLocationToMySQLWithFuture(context.getDeviceImei(), latitude, longitude);
Map<String, Object> map = buildLocationDataMap(latitude, longitude);
mqttGateway.sendMsgToMqtt(MqttConstants.GLOBAL_PUB_KEY + context.getDeviceImei(), 1, JsonUtils.toJsonString(map));
@ -114,31 +123,32 @@ public class BjqLocationDataRule implements MqttMessageRule {
public void asyncSendLocationToRedisWithFuture(String deviceImei, String latitude, String longitude) {
CompletableFuture.runAsync(() -> {
try {
if(StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)){
if (StringUtils.isBlank(latitude) || StringUtils.isBlank(longitude)) {
return;
}
String[] latArr = latitude.split("\\.");
String[] lonArr = longitude.split("\\.");
// 将位置信息存储到Redis中
String redisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DEVICE_LOCATION_KEY_PREFIX;
String redisObj = RedisUtils.getCacheObject(redisKey);
JSONObject jsonOBj = JSONObject.parseObject(redisObj);
if(jsonOBj != null){
String str1 = latArr[0] +"."+ latArr[1].substring(0,4);
String str2 = lonArr[0] +"."+ lonArr[1].substring(0,4);
String cacheLatitude = jsonOBj.getString("wgs84_latitude");
String cacheLongitude = jsonOBj.getString("wgs84_longitude");
String[] latArr1 = cacheLatitude.split("\\.");
String[] lonArr1 = cacheLongitude.split("\\.");
String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4);
String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4);
if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){
log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
return;
}
}
// String[] latArr = latitude.split("\\.");
// String[] lonArr = longitude.split("\\.");
// // 将位置信息存储到Redis中
String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX;
// String redisObj = RedisUtils.getCacheObject(redisKey);
// JSONObject jsonOBj = JSONObject.parseObject(redisObj);
// if(jsonOBj != null){
// String str1 = latArr[0] +"."+ latArr[1].substring(0,4);
// String str2 = lonArr[0] +"."+ lonArr[1].substring(0,4);
//
// String cacheLatitude = jsonOBj.getString("wgs84_latitude");
// String cacheLongitude = jsonOBj.getString("wgs84_longitude");
// String[] latArr1 = cacheLatitude.split("\\.");
// String[] lonArr1 = cacheLongitude.split("\\.");
//
// String cacheStr1 = latArr1[0] +"."+ latArr1[1].substring(0,4);
// String cacheStr2 = lonArr1[0] +"."+ lonArr1[1].substring(0,4);
// if(str1.equals(cacheStr1) && str2.equals(cacheStr2)){
// log.info("位置信息未发生变化: device={}, lat={}, lon={}", deviceImei, latitude, longitude);
// return;
// }
// }
// 构造位置信息对象
Map<String, Object> locationInfo = new LinkedHashMap<>();
@ -153,7 +163,6 @@ public class BjqLocationDataRule implements MqttMessageRule {
locationInfo.put("timestamp", System.currentTimeMillis());
String locationJson = JsonUtils.toJsonString(locationInfo);
// 存储到Redis
@ -171,12 +180,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)
*/
public void storeDeviceTrajectoryWithSortedSet(String deviceImei, String locationJson) {
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 locationJson = JsonUtils.toJsonString(locationInfo);
long timestamp = System.currentTimeMillis();
@ -194,20 +229,20 @@ public class BjqLocationDataRule implements MqttMessageRule {
log.error("存储设备轨迹到Redis(ZSet)失败: device={}, error={}", deviceImei, e.getMessage(), e);
}
}
private Map<String, Object> buildLocationDataMap(String latitude, String longitude) {
String[] latArr = latitude.split("\\.");
String[] lonArr = longitude.split("\\.");
ArrayList<Integer> intData = new ArrayList<>();
intData.add(11);
intData.add(Integer.parseInt(latArr[0]));
String str1 = latArr[1];
intData.add(Integer.parseInt(str1.substring(0,4)));
intData.add(Integer.parseInt(str1.substring(0, 4)));
String str2 = lonArr[1];
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.put("instruct", intData);
return map;

View File

@ -1,8 +1,16 @@
package com.fuyuanshen.global.mqtt.rule.bjq;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.core.utils.date.DurationUtils;
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.service.DeviceService;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
@ -13,9 +21,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
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.*;
/**
@ -26,6 +36,10 @@ import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
@Slf4j
public class BjqModeRule implements MqttMessageRule {
private final DeviceService deviceService;
private final IDeviceAlarmService deviceAlarmService;
@Override
public String getCommandType() {
return LightingCommandTypeConstants.LIGHT_MODE;
@ -39,14 +53,36 @@ public class BjqModeRule implements MqttMessageRule {
String mainLightMode = convertArr[1].toString();
String batteryRemainingTime = convertArr[2].toString();
if(StringUtils.isNotBlank(mainLightMode)){
if("0".equals(mainLightMode)){
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ context.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "0", Duration.ofSeconds(60*15));
if (StringUtils.isNotBlank(mainLightMode)) {
log.info("设备离线mainLightMode{}", mainLightMode);
if ("0".equals(mainLightMode)) {
// 设备离线
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + context.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX;
RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "0");
String sendMessageIng = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + context.getDeviceImei() + ":messageSending";
String messageSendingValue = RedisUtils.getCacheObject(sendMessageIng);
if ("1".equals(messageSendingValue)) {
// 设置为故障状态
RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "2");
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("device_imei", context.getDeviceImei());
updateWrapper.set("online_status", 2);
deviceService.update(updateWrapper);
RedisUtils.deleteObject(sendMessageIng);
// 解除告警
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX;
if (RedisUtils.getCacheObject(deviceRedisKey) != null) {
RedisUtils.deleteObject(deviceRedisKey);
}
cancelAlarm(context.getDeviceImei());
}
}
// 发送设备状态和位置信息到Redis
syncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),mainLightMode);
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_LIGHT_BRIGHTNESS_KEY_PREFIX;
syncSendDeviceDataToRedisWithFuture(context.getDeviceImei(), mainLightMode);
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_LIGHT_BRIGHTNESS_KEY_PREFIX;
// 存储到Redis
RedisUtils.setCacheObject(deviceRedisKey, batteryRemainingTime);
@ -63,13 +99,13 @@ public class BjqModeRule implements MqttMessageRule {
*
* @param deviceImei 设备IMEI
*/
public void syncSendDeviceDataToRedisWithFuture(String deviceImei,Object convertValue) {
public void syncSendDeviceDataToRedisWithFuture(String deviceImei, Object convertValue) {
// CompletableFuture.runAsync(() -> {
//
// });
try {
// 将设备状态信息存储到Redis中
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + deviceImei + DEVICE_LIGHT_MODE_KEY_PREFIX;
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + deviceImei + DEVICE_LIGHT_MODE_KEY_PREFIX;
// 存储到Redis
RedisUtils.setCacheObject(deviceRedisKey, convertValue.toString());
@ -80,5 +116,29 @@ public class BjqModeRule implements MqttMessageRule {
}
/**
* 解除告警
*
* @param deviceImei 设备IMEI
*/
public void cancelAlarm(String deviceImei) {
DeviceAlarmVo deviceAlarmVo = deviceAlarmService.queryLatestByDeviceImei(deviceImei);
DeviceAlarmBo deviceAlarmBo = new DeviceAlarmBo();
if (deviceAlarmVo != null) {
if (deviceAlarmVo.getFinishTime() == null) {
BeanUtil.copyProperties(deviceAlarmVo, deviceAlarmBo);
deviceAlarmBo.setFinishTime(new Date());
String durationBetween = DurationUtils.getDurationBetween(deviceAlarmVo.getStartTime(), deviceAlarmBo.getFinishTime());
deviceAlarmBo.setDurationTime(durationBetween);
// 0已处理1未处理
deviceAlarmBo.setTreatmentState(0);
// 告警状态0 解除告警, 1 告警中
deviceAlarmBo.setAlarmState(0);
deviceAlarmService.updateByBo(deviceAlarmBo);
}
}
}
}

View File

@ -43,17 +43,28 @@ public class BjqSendMessageRule implements MqttMessageRule {
public void execute(MqttRuleContext context) {
String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei();
try {
Byte val2 = (Byte) context.getConvertArr()[1];
// Byte val2 = (Byte) context.getConvertArr()[1];
String val2Str = context.getConvertArr()[1].toString();
int val2 = Integer.parseInt(val2Str);
System.out.println("收到设备信息命令:"+val2);
if (val2 == 100) {
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
return;
}
if(val2==200){
String sendMessageIng = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + context.getDeviceImei() + ":messageSending";
RedisUtils.deleteObject(sendMessageIng);
return;
}
String data = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + context.getDeviceImei() + ":app_send_message_data");
if (StringUtils.isEmpty(data)) {
return;
}
byte[] arr = ImageToCArrayConverter.convertStringToByteArray(data);
byte[] specificChunk = ImageToCArrayConverter.getChunk(arr, (val2 - 1), 512);
log.info("第{}块数据大小: {} 字节", val2, specificChunk.length);
@ -61,7 +72,7 @@ public class BjqSendMessageRule implements MqttMessageRule {
ArrayList<Integer> intData = new ArrayList<>();
intData.add(6);
intData.add((int) val2);
intData.add(val2);
ImageToCArrayConverter.buildArr(convertHexToDecimal(specificChunk), intData);
intData.add(0);
intData.add(0);

View File

@ -54,7 +54,7 @@ public class XinghanBootLogoRule implements MqttMessageRule {
@Override
public void execute(MqttRuleContext ctx) {
final String functionAccessKey = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
final String functionAccessKey = FUNCTION_ACCESS_KEY + "LOGO:" + ctx.getDeviceImei();
try {
MqttXinghanLogoJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), MqttXinghanLogoJson.class);
@ -62,6 +62,14 @@ public class XinghanBootLogoRule implements MqttMessageRule {
String respText = payload.getStaPicTrans();
log.warn("设备上报LOGO{}", respText);
// --- 去重 START ---
String dedupKey = "xd:MSG:LOGO:" + ctx.getDeviceImei() + ":" + respText;
boolean first = RedisUtils.setObjectIfAbsent(dedupKey, "1", Duration.ofSeconds(3));
if (!first) {
log.warn("重复消息丢弃 {}", dedupKey);
return;
}
// 1. great! —— 成功标记
if ("great!".equalsIgnoreCase(respText)) {
RedisUtils.setCacheObject(functionAccessKey,
@ -114,13 +122,24 @@ public class XinghanBootLogoRule implements MqttMessageRule {
private static final int CHUNK_SIZE = 256;
/**
* 计算数据的CRC32校验值并将结果转换为整数列表
*
* @param data 需要计算CRC32校验值的字节数组
* @return 包含CRC32校验值的整数列表每个字节对应一个无符号整数
*/
private static ArrayList<Integer> crc32AsList(byte[] data) {
// 计算CRC32校验值
CRC32 crc = new CRC32();
crc.update(data);
// 将CRC32值转换为字节数组
byte[] crcBytes = ByteBuffer.allocate(4)
.order(ByteOrder.BIG_ENDIAN)
.putInt((int) crc.getValue())
.array();
// 将字节数组转换为无符号整数列表
ArrayList<Integer> list = new ArrayList<>(4);
for (byte b : crcBytes) {
list.add(Byte.toUnsignedInt(b));
@ -128,6 +147,7 @@ public class XinghanBootLogoRule implements MqttMessageRule {
return list;
}
/* ---------- DTO ---------- */
@Data

View File

@ -1,31 +1,45 @@
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.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
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.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.LngLonUtil;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.base.MqttXinghanJson;
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.listener.domain.FunctionAccessStatus;
import com.fuyuanshen.web.enums.AlarmTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
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.DEVICE_KEY_PREFIX;
@ -55,18 +69,24 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
@Autowired
private ObjectMapper objectMapper;
private final IDeviceAlarmService deviceAlarmService;
private final DeviceService deviceService;
private final DeviceAlarmMapper deviceAlarmMapper;
@Override
public void execute(MqttRuleContext context) {
String functionAccess = FUNCTION_ACCESS_KEY + "DATA:" + context.getDeviceImei();
try {
// Latitude, longitude
//主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间
MqttXinghanJson deviceStatus = objectMapper.convertValue(context.getPayloadDict(), MqttXinghanJson.class);
deviceStatus.setBatteryPercentage(deviceStatus.getStaPowerPercent().toString());
// 发送设备状态和位置信息到Redis
asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
} catch (Exception e) {
log.error("处理上报数据命令时出错", e);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
}
}
@ -94,12 +114,141 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
// 异步发送经纬度到Redis
asyncSendLocationToRedisWithFuture(deviceImei, deviceStatus.getStalatitude(), deviceStatus.getStalongitude());
// 保存报警信息
saveAlarm(deviceImei,deviceStatus);
} catch (Exception 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);
String lockKey = redisKey + ":lock"; // 分布式锁 key
RedissonClient client = RedisUtils.getClient(); // 唯一用到的“旧”入口
RLock lock = client.getLock(lockKey);
// ---------- 情况 1当前正在报警 ----------
if (nowAlarming) {
// 已存在未结束报警 -> 什么都不做(同一条报警)
if (alarmId != null) {
// key 还在 -> 同一条报警,只续期
RedisUtils.setCacheObject(redisKey, alarmId, Duration.ofMinutes(10));
return;
}
// 需要新建,抢锁
boolean locked = false;
try {
locked = lock.tryLock(3, TimeUnit.SECONDS); // 最多等 3 s
if (!locked) { // 抢不到直接放弃
return;
}
// 锁内二次校验double-check
alarmId = RedisUtils.getCacheObject(redisKey);
if (alarmId != null) {
return; // 并发线程已建好
}
// 不存在 -> 新建
DeviceAlarmBo bo = createAlarmBo(deviceImei, type);
if (bo == null){
return;
}
deviceAlarmService.insertByBo(bo);
RedisUtils.setCacheObject(redisKey, bo.getId(), Duration.ofMinutes(10)); // 5分钟后结束过期
}catch (InterruptedException ignore) {
// 立即中断并退出,禁止继续往下走
Thread.currentThread().interrupt();
} finally {
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
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
*

View File

@ -0,0 +1,125 @@
package com.fuyuanshen.global.mqtt.rule.xinghan;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.DEVICE_ALARM_MESSAGE_KEY_PREFIX;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
/**
* 星汉设备发送紧急通知 下发规则:
* <p>
* 1. 设备上行 sta_BreakNews=cover! => 仅标记成功<br>
* 2. 设备上行 sta_BreakNews=数字 => GBK编码每行文字为一包一共4包第一字节为包序号
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class XinghanSendAlarmMessageRule implements MqttMessageRule {
private final MqttGateway mqttGateway;
private final ObjectMapper objectMapper;
@Override
public String getCommandType() {
return XingHanCommandTypeConstants.XingHan_BREAK_NEWS;
}
@Override
public void execute(MqttRuleContext ctx) {
String functionAccess = FUNCTION_ACCESS_KEY + "ALARM:" + ctx.getDeviceImei();
try {
XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson.class);
String respText = payload.getStaBreakNews();
log.info("设备上报紧急通知握手: {} ", respText);
// --- 去重 START ---
String dedupKey = "xd:ALARM:dedup:" + ctx.getDeviceImei() + ":" + respText;
boolean first = RedisUtils.setObjectIfAbsent(dedupKey, "1", Duration.ofSeconds(3));
if (!first) {
log.warn("重复消息丢弃 {}", dedupKey);
return;
}
// 1. cover! —— 成功标记
if ("cover!".equalsIgnoreCase(respText)) {
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
log.info("设备 {} 发送紧急通知完成", ctx.getDeviceImei());
return;
}
// 2. 数字 —— 下发数据块
int blockIndex;
try {
blockIndex = Integer.parseInt(respText);
} catch (NumberFormatException ex) {
log.warn("设备 {} 紧急通知上报非法块号:{}", ctx.getDeviceImei(), respText);
return;
}
// 将发送的信息原文本以List<String>形式存储在Redis中
String data = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + ctx.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX);
if (data == null || data.isEmpty()) {
return;
}
//
ArrayList<Integer> intData = new ArrayList<>();
intData.add(blockIndex);
// 获取块原内容 转成GBK 再转成无符号十进制整数
String blockTxt = data.split("\\|")[blockIndex-1];
// 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数
for (byte b : blockTxt.getBytes(GBK)) {
intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数
}
Map<String, Object> map = new HashMap<>();
map.put("ins_BreakNews", intData);
String topic = MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei();
String json = JsonUtils.toJsonString(map);
mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("发送设备紧急通知=>topic:{},payload:{}",
MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei(),
JsonUtils.toJsonString(map));
} catch (Exception e) {
log.error("处理发送设备紧急通知时出错", e);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
}
}
private static final Charset GBK = Charset.forName("GBK");
/* ---------- DTO ---------- */
@Data
private static class MqttXinghanAlarmMsgJson {
/**
* 设备上行:
* 数字 -> 请求对应块号
* cover! -> 写入成功
*/
@JsonProperty("sta_BreakNews")
private String staBreakNews;
}
}

View File

@ -17,10 +17,7 @@ import org.springframework.stereotype.Component;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
@ -42,12 +39,18 @@ public class XinghanSendMsgRule implements MqttMessageRule {
@Override
public String getCommandType() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 三种等价的写法
Integer[] array1 = list.toArray(new Integer[0]);
Integer[] array2 = list.toArray(size -> new Integer[size]);
return XingHanCommandTypeConstants.XingHan_ESEND_MSG;
}
@Override
public void execute(MqttRuleContext ctx) {
String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
String functionAccess = FUNCTION_ACCESS_KEY + "MSG:" + ctx.getDeviceImei();
try {
XinghanSendMsgRule.MqttXinghanMsgJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), XinghanSendMsgRule.MqttXinghanMsgJson.class);
@ -55,9 +58,17 @@ public class XinghanSendMsgRule implements MqttMessageRule {
String respText = payload.getStaTexTrans();
log.info("设备上报人员信息: {} ", respText);
// --- 去重 START ---
String dedupKey = "xd:MSG:dedup:" + ctx.getDeviceImei() + ":" + respText;
boolean first = RedisUtils.setObjectIfAbsent(dedupKey, "1", Duration.ofSeconds(3));
if (!first) {
log.warn("重复消息丢弃 {}", dedupKey);
return;
}
// 1. genius! —— 成功标记
if ("genius!".equalsIgnoreCase(respText)) {
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
log.info("设备 {} 发送消息完成", ctx.getDeviceImei());
return;
}
@ -79,6 +90,7 @@ public class XinghanSendMsgRule implements MqttMessageRule {
intData.add(blockIndex);
// 获取块原内容 转成GBK 再转成无符号十进制整数
String blockTxt = data.get(blockIndex-1);
log.warn("设备上报人员登记信息:{}", blockTxt);
// 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数
for (byte b : blockTxt.getBytes(GBK)) {
intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数

View File

@ -1,9 +1,13 @@
package com.fuyuanshen.global.queue;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
@ -15,6 +19,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
@Service
@Slf4j
public class MqttMessageConsumer {
@ -27,7 +33,7 @@ public class MqttMessageConsumer {
private ExecutorService messageProcessorPool = Executors.newFixedThreadPool(10);
// 初始化方法,启动消息监听
// @PostConstruct
@PostConstruct
public void start() {
log.info("启动MQTT消息消费者...");
// 启动消息获取线程
@ -62,7 +68,7 @@ public class MqttMessageConsumer {
public void consumeMessages() {
String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY;
String threadName = Thread.currentThread().getName();
log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey);
// log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey);
try {
while (!Thread.currentThread().isInterrupted() && !messageConsumerPool.isShutdown()) {
@ -75,7 +81,7 @@ public class MqttMessageConsumer {
);
if (message != null) {
log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message);
// log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message);
// 将消息处理任务提交到处理线程池
messageProcessorPool.submit(() -> processMessage(message));
}
@ -92,15 +98,32 @@ public class MqttMessageConsumer {
String threadName = Thread.currentThread().getName();
try {
log.info("业务处理线程 {} 开始处理消息: {}", threadName, message);
// 实现具体的业务逻辑
// 例如更新数据库、发送通知等
// String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
// String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey);
// 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.eq("device_imei", message)
// .eq("online_status", 0)
// .set("online_status", 1);
// deviceMapper.update(updateWrapper);
// }
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("device_imei", message)
.in("online_status", 0,2)
.set("online_status", 1);
deviceMapper.update(updateWrapper);
int update = deviceMapper.update(updateWrapper);
// 模拟业务处理耗时
Thread.sleep(200);
// Thread.sleep(200);
log.info("业务处理线程 {} 完成消息处理: {}", threadName, message);
} catch (Exception e) {

View File

@ -1,6 +1,8 @@
package com.fuyuanshen.global.queue;
import com.fuyuanshen.common.core.constant.GlobalConstants;
public class MqttMessageQueueConstants {
public static final String MQTT_MESSAGE_QUEUE_KEY = "mqtt:message:queue";
public static final String MQTT_MESSAGE_DEDUP_KEY = "mqtt:message:dedup";
public static final String MQTT_MESSAGE_QUEUE_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "mqtt:message:queue";
public static final String MQTT_MESSAGE_DEDUP_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "mqtt:message:dedup";
}

View File

@ -0,0 +1,40 @@
package com.fuyuanshen.global.queue;// ScheduledTasks.java
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.vo.OnlineStatusVo;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
@Component
public class OnlineStatusTask {
@Autowired
private DeviceMapper deviceMapper;
// 使用cron表达式每分钟的第0秒执行
@Scheduled(cron = "0 */3 * * * ?")
public void cronTask() {
List<OnlineStatusVo> onlineStatusVos = deviceMapper.queryOnlineStatusList();
onlineStatusVos.forEach(item -> {
String onlineStatusKey = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX;
String status = RedisUtils.getCacheObject(onlineStatusKey);
if(StringUtils.isBlank(status) || "0".equals(status)){
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", item.getId());
updateWrapper.set("online_status", 0);
deviceMapper.update(updateWrapper);
}
});
}
}

View File

@ -63,10 +63,11 @@ public class CaptchaController {
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
String templateId = "SMS_322180518";
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
// SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map);
if (!smsResponse.isSuccess()) {
log.error("验证码短信发送异常 => {}", smsResponse);

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

@ -82,6 +82,22 @@ public class DeviceBJQController extends BaseController {
return R.ok();
}
/**
* 批量上传设备logo图片
*/
@PostMapping("/batchUploadLogo")
@FunctionAccessAnnotation("batchUploadLogo")
public R<Void> batchUploadLogo(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) {
MultipartFile file = bo.getFile();
if(file.getSize()>1024*1024*2){
return R.warn("图片不能大于2M");
}
appDeviceService.batchUploadLogo(bo);
return R.ok();
}
/**
* 灯光模式
* 0关灯1强光模式2弱光模式, 3爆闪模式, 4泛光模式

View File

@ -39,7 +39,7 @@ public class DeviceChargeDischargeController extends BaseController {
/**
* 查询设备充放电记录列表
*/
@SaCheckPermission("equipment:chargeDischarge:list")
// @SaCheckPermission("equipment:chargeDischarge:list")
@GetMapping("/list")
public TableDataInfo<DeviceChargeDischargeVo> list(DeviceChargeDischargeBo bo, PageQuery pageQuery) {
return deviceChargeDischargeService.queryPageList(bo, pageQuery);
@ -48,7 +48,7 @@ public class DeviceChargeDischargeController extends BaseController {
/**
* 设备充放电记录列表
*/
@SaCheckPermission("equipment:chargeDischarge:export")
// @SaCheckPermission("equipment:chargeDischarge:export")
@Log(title = "设备充放电记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(DeviceChargeDischargeBo bo, HttpServletResponse response) {
@ -61,7 +61,7 @@ public class DeviceChargeDischargeController extends BaseController {
*
* @param id 主键
*/
@SaCheckPermission("equipment:chargeDischarge:query")
// @SaCheckPermission("equipment:chargeDischarge:query")
@GetMapping("/{id}")
public R<DeviceChargeDischargeVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
@ -71,7 +71,7 @@ public class DeviceChargeDischargeController extends BaseController {
/**
* 新增设备充放电记录
*/
@SaCheckPermission("equipment:chargeDischarge:add")
// @SaCheckPermission("equipment:chargeDischarge:add")
@Log(title = "设备充放电记录", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
@ -82,7 +82,7 @@ public class DeviceChargeDischargeController extends BaseController {
/**
* 修改设备充放电记录
*/
@SaCheckPermission("equipment:chargeDischarge:edit")
// @SaCheckPermission("equipment:chargeDischarge:edit")
@Log(title = "设备充放电记录", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
@ -95,11 +95,13 @@ public class DeviceChargeDischargeController extends BaseController {
*
* @param ids 主键串
*/
@SaCheckPermission("equipment:chargeDischarge:remove")
// @SaCheckPermission("equipment:chargeDischarge:remove")
@Log(title = "设备充放电记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(deviceChargeDischargeService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@ -0,0 +1,161 @@
package com.fuyuanshen.web.controller.device;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fuyuanshen.app.domain.bo.AppOperationVideoBo;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.AppFileDto;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.log.enums.BusinessType;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria;
import com.fuyuanshen.equipment.domain.vo.AppDeviceVo;
import com.fuyuanshen.equipment.domain.vo.WebDeviceVo;
import com.fuyuanshen.web.domain.Dto.DeviceDebugEditDto;
import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto;
import com.fuyuanshen.web.domain.vo.DeviceInfoVo;
import com.fuyuanshen.web.service.device.DeviceBizService;
import com.fuyuanshen.web.service.device.DeviceDebugService;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 联调中心
*
* @author Lion Li
* @date 2025-08-28
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("api/device/debug")
public class DeviceDebugController extends BaseController {
private final DeviceBizService appDeviceService;
private final DeviceXinghanBizService deviceXinghanBizService;
private final DeviceDebugService deviceDebugService;
/**
* 查询设备列表
*/
@GetMapping("/list")
public TableDataInfo<WebDeviceVo> list(DeviceQueryCriteria bo, PageQuery pageQuery) {
return appDeviceService.queryWebDeviceList(bo, pageQuery);
}
/**
* 上传文件
*/
@Log(title = "批量上传文件")
@PostMapping("/addFile")
public R<Void> uploadFile(@Validated @ModelAttribute AppFileDto bo) throws IOException {
return toAjax(deviceDebugService.addFileHash(bo,true));
}
/**
* 操作视频添加
*/
@Log(title = "批量添加操作视频")
@PostMapping("/addVideo")
public R<Void> addOperationVideo(@RequestBody AppOperationVideoBo bo) {
return toAjax(deviceDebugService.addVideoList(bo));
}
/**
* 上传设备logo图片
*/
@Log(title = "批量上传设备logo图片")
@PostMapping("/addLogo")
@FunctionAccessAnnotation("uploadLogo")
public R<Void> uploadLogo670(@Validated @ModelAttribute DeviceDebugLogoUploadDto bo) {
MultipartFile file = bo.getFile();
if(file.getSize()>1024*1024*2){
return R.warn("图片不能大于2M");
}
deviceXinghanBizService.uploadDeviceLogoBatch(bo);
return R.ok();
}
/**
* 设备详情
*/
@Operation(summary = "设备详情")
@GetMapping(value = "/detail/{id}")
public R<DeviceInfoVo> getDeviceInfo(@PathVariable Long id) {
return R.ok(deviceDebugService.getDeviceInfo(id));
}
/**
* 修改设备联调信息
*/
@Log(title = "修改设备联调信息")
@PostMapping("/editDebug")
public R<Void> editDeviceDebug(@Validated @ModelAttribute DeviceDebugEditDto bo) throws Exception {
// 1. 基础参数必填校验
validateDeviceDebugEdit(bo);
// 修改上传设备说明
if (bo.getExplanationFiles() != null) {
AppFileDto appFileDto = new AppFileDto();
appFileDto.setDeviceIds(new Long[]{ bo.getDeviceId() });
appFileDto.setFileType(1L);
appFileDto.setFiles(bo.getExplanationFiles());
deviceDebugService.addFileHash(appFileDto,false);
}
// 修改上传设备参数
if (bo.getParameterFiles() != null) {
AppFileDto appFileDto = new AppFileDto();
appFileDto.setDeviceIds(new Long[]{ bo.getDeviceId() });
appFileDto.setFileType(2L);
appFileDto.setFiles(bo.getParameterFiles());
deviceDebugService.addFileHash(appFileDto,false);
}
deviceDebugService.delFile(bo.getFileIds());
// 修改操作视频
if (bo.getVideoUrl().isEmpty()) {
AppOperationVideoBo appOperationVideoBo = new AppOperationVideoBo();
appOperationVideoBo.setDeviceIds(new Long[]{ bo.getDeviceId() });
appOperationVideoBo.setVideoUrl(bo.getVideoUrl());
deviceDebugService.addVideoList(appOperationVideoBo);
}
// 修改设备logo 每个型号设备走不同协议无法共用同一个上传
// if(bo.getLogoFile() != null){
// MultipartFile file = bo.getLogoFile();
// if(file.getSize()>1024*1024*2){
// return R.warn("图片不能大于2M");
// }
// AppDeviceLogoUploadDto bo1 = new AppDeviceLogoUploadDto();
// bo1.setDeviceId(bo.getDeviceId());
// bo1.setDeviceImei(bo.getDeviceImei());
// bo1.setFile(file);
// deviceXinghanBizService.uploadDeviceLogo(bo1);
// }
return R.ok();
}
/* ------------------ 私有复用 ------------------ */
private void validateDeviceDebugEdit(DeviceDebugEditDto bo) {
if (bo.getDeviceId() == null || bo.getDeviceId() == 0L) {
throw new ServiceException("请选择设备");
}
// if (bo.getDeviceImei().isEmpty()) {
// throw new ServiceException("设备 IMEI 不能为空");
// }
}
}

View File

@ -1,18 +1,32 @@
package com.fuyuanshen.web.controller.device;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.RandomUtil;
import com.fuyuanshen.app.domain.bo.AppDeviceShareBo;
import com.fuyuanshen.app.domain.vo.AppDeviceShareVo;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.ratelimiter.annotation.RateLimiter;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
import com.fuyuanshen.web.service.DeviceShareService;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
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.RestController;
import org.springframework.web.bind.annotation.*;
import java.time.Duration;
import java.util.LinkedHashMap;
import static com.fuyuanshen.common.core.constant.GlobalConstants.DEVICE_SHARE_CODES_KEY;
/**
* 设备分享管理
@ -26,18 +40,66 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("api/equipment/share")
public class DeviceShareController extends BaseController {
private final DeviceShareService appDeviceShareService;
private final DeviceShareService deviceShareService;
/**
*查询设备分享列表(web)
* 分享管理列表
*/
// @SaCheckPermission("equipment:alarm:list")
@GetMapping("/list")
@GetMapping("/deviceShareList")
public TableDataInfo<AppDeviceShareVo> list(AppDeviceShareBo bo, PageQuery pageQuery) {
return appDeviceShareService.queryWebList(bo, pageQuery);
return deviceShareService.queryWebPageList(bo, pageQuery);
}
/**
* 新增设备分享
*/
@RepeatSubmit()
@PostMapping("/deviceShare")
public R<Void> deviceShare(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) {
return toAjax(deviceShareService.deviceShare(bo));
}
/**
* 权限管理
*/
@RepeatSubmit()
@PostMapping("/permission")
public R<Void> permission(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) {
return toAjax(deviceShareService.permission(bo));
}
/**
* 移除分享用户
*
* @param ids 主键串
*/
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(deviceShareService.remove(ids));
}
/**
* 短信验证码
*
* @param phonenumber 用户手机号
*/
@SaIgnore
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = DEVICE_SHARE_CODES_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map);
if (!smsResponse.isSuccess()) {
return R.fail(smsResponse.getData().toString());
}
return R.ok();
}
}

View File

@ -0,0 +1,163 @@
package com.fuyuanshen.web.controller.device;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.alibaba.fastjson.JSON;
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.web.domain.Dto.DeviceXinghanInstructDto;
import com.fuyuanshen.web.domain.Dto.SystemVersionDto;
import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.time.Duration;
import java.util.List;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_ALARM_MESSAGE_KEY_PREFIX;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
/**
* 设备控制类 HBY670
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/xinghan/device")
public class DeviceXinghanController extends BaseController {
private final DeviceXinghanBizService deviceXinghanBizService;
/**
* 获取设备详细信息
*
* @param id 主键
*/
@GetMapping("/{id}")
public R<DeviceXinghanDetailVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(deviceXinghanBizService.getInfo(id));
}
/**
* 人员信息登记
*/
@PostMapping(value = "/registerPersonInfo")
// @FunctionAccessAnnotation("registerPersonInfo")
public R<Void> registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) {
return toAjax(deviceXinghanBizService.registerPersonInfo(bo));
}
/**
* 发送紧急通知
*/
@PostMapping(value = "/sendAlarmMessage")
@FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10)
public R<Void> sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) {
return toAjax(deviceXinghanBizService.sendAlarmMessage(bo));
}
/**
* 上传设备logo图片
*/
@PostMapping("/uploadLogo")
@FunctionAccessAnnotation("uploadLogo")
public R<Void> upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) {
MultipartFile file = bo.getFile();
if(file.getSize()>1024*1024*2){
return R.warn("图片不能大于2M");
}
deviceXinghanBizService.uploadDeviceLogo(bo);
return R.ok();
}
/**
* 静电预警档位
* 3,2,1,0,分别表示高档/中档/低挡/关闭
*/
@PostMapping("/DetectGradeSettings")
public R<Void> DetectGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.upDetectGradeSettings(params);
return R.ok();
}
/**
* 照明档位
* 照明档位2,1,0,分别表示弱光/强光/关闭
*/
@PostMapping("/LightGradeSettings")
public R<Void> LightGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.upLightGradeSettings(params);
return R.ok();
}
/**
* SOS档位
* SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/
@PostMapping("/SOSGradeSettings")
public R<Void> SOSGradeSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.upSOSGradeSettings(params);
return R.ok();
}
/**
* SOS档位 批量
* SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/
@PostMapping("/SOSGradeSettingsBatch")
public R<Void> SOSGradeSettingsBatch(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.sendCommandBatch(params,"ins_SOSGrade","SOS档位");
return R.ok();
}
/**
* 静止报警状态
* 静止报警状态0-未静止报警1-正在静止报警。
*/
@PostMapping("/ShakeBitSettings")
public R<Void> ShakeBitSettings(@RequestBody DeviceXinghanInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.upShakeBitSettings(params);
return R.ok();
}
/**
* 版本更新
*/
@SaCheckPermission("system:appVersion:up")
@PostMapping("/UpVersion")
public R<Void> VersionSettings(@RequestBody List<SystemVersionDto> params) {
// params 已经是 List<SystemVersionDto>
// 2. 转 JSON 并写 Rediskey 自己定,带个过期时间
String versionKey = GLOBAL_REDIS_KEY + "System:Version:Xinghan";
String json = JSON.toJSONString(params); // fastjson
if (StringUtils.isBlank(json)) { // hutool 工具,可用 StringUtils.isBlank 代替
return R.fail("信息无效");
}
// 缓存告警消息到Redis
RedisUtils.setCacheObject(versionKey, json, Duration.ofDays(30));
return R.ok();
}
}

View File

@ -1,17 +1,16 @@
package com.fuyuanshen.web.controller.device;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.equipment.domain.vo.AlarmInformationVo;
import com.fuyuanshen.equipment.domain.vo.DataOverviewVo;
import com.fuyuanshen.equipment.domain.vo.EquipmentClassificationVo;
import com.fuyuanshen.equipment.service.DeviceService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
/**
* 首页数据
@ -39,29 +38,38 @@ public class HomePageController {
}
// 获取设备使用数据
@GetMapping("/{deviceId}")
public Map<String, Object> getUsageData(
@PathVariable String deviceId,
@RequestParam String range) {
Map<String, Object> response = new HashMap<>();
if ("halfYear".equals(range)) {
response.put("months", Arrays.asList("1月", "2月", "3月", "4月", "5月", "6月"));
response.put("data", Arrays.asList(45, 52, 38, 60, 56, 48));
} else if ("oneYear".equals(range)) {
response.put("months", Arrays.asList("7月", "8月", "9月", "10月", "11月", "12月",
"1月", "2月", "3月", "4月", "5月", "6月"));
response.put("data", Arrays.asList(42, 38, 45, 48, 52, 55, 45, 52, 38, 60, 56, 48));
}
response.put("deviceId", deviceId);
response.put("range", range);
response.put("lastUpdate", new Date());
return response;
/**
* 获取 设备分类
* DataOverview
*/
@GetMapping("/getEquipmentClassification")
public R<EquipmentClassificationVo> getEquipmentClassification() {
return R.ok(deviceService.getEquipmentClassification());
}
/**
* 获取 报警信息
* DataOverview
*/
@GetMapping("/getAlarmInformation")
public R<AlarmInformationVo> getAlarmInformation() {
return R.ok(deviceService.getAlarmInformation());
}
/**
* 获取设备使用数据
*
* @param deviceTypeId 设备ID (可选)
* @param range 时间范围 1:半年 2:一年
* @return 每月使用数据列表
*/
@GetMapping("/getEquipmentUsageData/{range}")
public R<List<Map<String, Object>>> getEquipmentUsageData(@PathVariable Integer range,
@RequestParam(required = false) Long deviceTypeId) {
return R.ok(deviceService.getEquipmentUsageData(deviceTypeId, range));
}
}

View File

@ -3,6 +3,8 @@ package com.fuyuanshen.web.controller.device;
import com.fuyuanshen.app.domain.AppPersonnelInfoRecords;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.DeviceLog;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
@ -65,7 +67,7 @@ public class WEBDeviceController extends BaseController {
* @param deviceId
* @return
*/
@Operation(summary = "设备详情")
@Operation(summary = "设备用户详情")
@GetMapping(value = "/getDeviceUser/{deviceId}")
public R<List<AppPersonnelInfoRecords>> getDeviceUser(@PathVariable Long deviceId) {
List<AppPersonnelInfoRecords> device = deviceService.getDeviceUser(deviceId);
@ -77,13 +79,18 @@ public class WEBDeviceController extends BaseController {
* 设备操作记录
*
* @param deviceId
* @param startTime 开始时间
* @param endTime 结束时间
* @return
*/
@Operation(summary = "设备操作记录")
@GetMapping(value = "/getOperationRecord/{deviceId}")
public R<List<DeviceLog>> getOperationRecord(@PathVariable Long deviceId) {
List<DeviceLog> device = deviceService.getOperationRecord(deviceId);
return R.ok(device);
public TableDataInfo<DeviceLog> getOperationRecord(@PathVariable Long deviceId,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
PageQuery pageQuery) {
TableDataInfo<DeviceLog> device = deviceService.getOperationRecord(deviceId, startTime, endTime,pageQuery);
return device;
}
@ -91,13 +98,17 @@ public class WEBDeviceController extends BaseController {
* 设备告警记录
*
* @param deviceId
* @param startTime 开始时间
* @param endTime 结束时间
* @return
*/
@Operation(summary = "设备告警记录")
@GetMapping(value = "/getAlarmRecord/{deviceId}")
public R<List<DeviceAlarmVo>> getAlarmRecord(@PathVariable Long deviceId) {
List<DeviceAlarmVo> device = deviceService.getAlarmRecord(deviceId);
return R.ok(device);
public TableDataInfo<DeviceAlarmVo> getAlarmRecord(@PathVariable Long deviceId,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
PageQuery pageQuery) {
return deviceService.getAlarmRecord(deviceId, startTime, endTime, pageQuery);
}

View File

@ -0,0 +1,110 @@
package com.fuyuanshen.web.controller.device.fence;
import java.util.List;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo;
import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.log.enums.BusinessType;
import com.fuyuanshen.common.excel.utils.ExcelUtil;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
/**
* 围栏进出记录
*
* @author Lion Li
* @date 2025-09-11
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/equipment/fenceAccessRecord")
public class DeviceFenceAccessRecordController extends BaseController {
private final IDeviceFenceAccessRecordService deviceFenceAccessRecordService;
/**
* 查询围栏进出记录列表
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:list")
@GetMapping("/list")
public TableDataInfo<DeviceFenceAccessRecordVo> list(DeviceFenceAccessRecordBo bo, PageQuery pageQuery) {
return deviceFenceAccessRecordService.queryPageList(bo, pageQuery);
}
/**
* 导出围栏进出记录列表
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:export")
@Log(title = "导出围栏进出记录列表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(DeviceFenceAccessRecordBo bo, HttpServletResponse response) {
List<DeviceFenceAccessRecordVo> list = deviceFenceAccessRecordService.queryList(bo);
ExcelUtil.exportExcel(list, "围栏进出记录", DeviceFenceAccessRecordVo.class, response);
}
/**
* 获取围栏进出记录详细信息
*
* @param id 主键
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:query")
@GetMapping("/{id}")
public R<DeviceFenceAccessRecordVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(deviceFenceAccessRecordService.queryById(id));
}
/**
* 新增围栏进出记录
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:add")
@Log(title = "围栏进出记录", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody DeviceFenceAccessRecordBo bo) {
return toAjax(deviceFenceAccessRecordService.insertByBo(bo));
}
/**
* 修改围栏进出记录
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:edit")
@Log(title = "围栏进出记录", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody DeviceFenceAccessRecordBo bo) {
return toAjax(deviceFenceAccessRecordService.updateByBo(bo));
}
/**
* 删除围栏进出记录
*
* @param ids 主键串
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:remove")
@Log(title = "围栏进出记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(deviceFenceAccessRecordService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@ -0,0 +1,132 @@
package com.fuyuanshen.web.controller.device.fence;
import java.util.List;
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
import com.fuyuanshen.equipment.service.IDeviceGeoFenceService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.log.enums.BusinessType;
import com.fuyuanshen.common.excel.utils.ExcelUtil;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
/**
* 电子围栏
*
* @author Lion Li
* @date 2025-09-11
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/equipment/geoFence")
public class DeviceGeoFenceController extends BaseController {
private final IDeviceGeoFenceService deviceGeoFenceService;
/**
* 查询电子围栏列表
*/
@SaCheckPermission("fys-equipment:geoFence:list")
@GetMapping("/list")
public TableDataInfo<DeviceGeoFenceVo> list(DeviceGeoFenceBo bo, PageQuery pageQuery) {
return deviceGeoFenceService.queryPageList(bo, pageQuery);
}
/**
* 导出电子围栏列表
*/
@SaCheckPermission("fys-equipment:geoFence:export")
@Log(title = "导出电子围栏列表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(DeviceGeoFenceBo bo, HttpServletResponse response) {
List<DeviceGeoFenceVo> list = deviceGeoFenceService.queryList(bo);
// 设置区域类型名称
list.forEach(item -> {
item.setAreaTypeNameByAreaType();
item.setIsActiveNameByIsActive(); // 添加这行来设置激活状态名称
});
ExcelUtil.exportExcel(list, "电子围栏", DeviceGeoFenceVo.class, response);
}
/**
* 获取电子围栏详细信息
*
* @param id 主键
*/
@SaCheckPermission("fys-equipment:geoFence:query")
@GetMapping("/{id}")
public R<DeviceGeoFenceVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(deviceGeoFenceService.queryById(id));
}
/**
* 新增电子围栏
*/
@SaCheckPermission("fys-equipment:geoFence:add")
@Log(title = "电子围栏", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody DeviceGeoFenceBo bo) {
return toAjax(deviceGeoFenceService.insertByBo(bo));
}
/**
* 修改电子围栏
*/
@SaCheckPermission("fys-equipment:geoFence:edit")
@Log(title = "电子围栏", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody DeviceGeoFenceBo bo) {
return toAjax(deviceGeoFenceService.updateByBo(bo));
}
/**
* 删除电子围栏
*
* @param ids 主键串
*/
@SaCheckPermission("fys-equipment:geoFence:remove")
@Log(title = "电子围栏", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(deviceGeoFenceService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 电子围栏-位置检查
*
* @param request
* @return
*/
@PostMapping("/check")
public ResponseEntity<FenceCheckResponse> checkPosition(
@Valid @RequestBody FenceCheckRequest request) {
FenceCheckResponse response = deviceGeoFenceService.checkPosition(request);
return ResponseEntity.ok(response);
}
}

View File

@ -0,0 +1,36 @@
package com.fuyuanshen.web.domain.Dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Data
public class DeviceDebugEditDto {
/**
* 设备主键列表
*/
private Long deviceId;
/**
* 设备 IMEI
*/
//private String deviceImei;
/**
* 上传 logo 图片
*/
//private MultipartFile LogoFile; // 同一张图
/**
* 参数文件
*/
private MultipartFile[] parameterFiles;
/**
* 说明文件
*/
private MultipartFile[] explanationFiles;
/**
* 视频链接
*/
private String videoUrl;
private List<Long> fileIds;
}

View File

@ -0,0 +1,18 @@
package com.fuyuanshen.web.domain.Dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Data
public class DeviceDebugLogoUploadDto {
/**
* 设备主键列表
*/
private List<Long> deviceIds; // 设备主键列表
/**
* 上传 图片
*/
private MultipartFile file; // 同一张图
}

View File

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

View File

@ -0,0 +1,10 @@
package com.fuyuanshen.web.domain.Dto;
import lombok.Data;
@Data
public class SystemVersionDto {
private String dictValue;
private String dictLabel;
private String remark;
}

View File

@ -0,0 +1,27 @@
package com.fuyuanshen.web.domain.vo;
import com.fuyuanshen.app.domain.vo.AppBusinessFileVo;
import com.fuyuanshen.app.domain.vo.AppFileVo;
import com.fuyuanshen.app.domain.vo.AppOperationVideoVo;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.vo.AppDeviceVo;
import lombok.Data;
import java.util.List;
/**
* 设备信息 视图对象
*
* @author Michelle.Chung
*/
@Data
public class DeviceInfoVo {
/**
* 设备业务文件
*/
private List<AppFileVo> appBusinessFileVoList;
/**
* 设备操作视频
*/
private List<AppOperationVideoVo> appOperationVideoVoList;
}

View File

@ -0,0 +1,103 @@
package com.fuyuanshen.web.domain.vo;
import cn.idev.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
import lombok.Data;
@Data
public class DeviceXinghanDetailVo {
/**
* 设备ID
*/
@ExcelProperty(value = "设备ID")
private Long deviceId;
/**
* 设备名称
*/
private String deviceName;
/**
* 设备IMEI
*/
private String deviceImei;
/**
* 设备MAC
*/
private String deviceMac;
/**
* 通讯方式 0:4G;1:蓝牙
*/
private Integer communicationMode;
/**
* 设备图片
*/
private String devicePic;
/**
* 设备类型
*/
private String typeName;
/**
* 蓝牙名称
*/
private String bluetoothName;
/**
* 设备状态
* 0 失效
* 1 正常
*/
private Integer deviceStatus;
/**
* 人员信息
*/
private AppPersonnelInfoVo personnelInfo;
/**
* 在线状态(0离线1在线)
*/
private Integer onlineStatus;
// 经度
private String longitude;
// 纬度
private String latitude;
// 逆解析地址
private String address;
/**
* 第一键值对静电预警档位3,2,1,0,分别表示高档/中档/低挡/关闭.
*/
private Integer staDetectGrade;
/**
* 第二键值对照明档位2,1,0,分别表示弱光/强光/关闭
*/
private Integer staLightGrade;
/**
* 第三键值对SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/
public Integer staSOSGrade;
/**
* 第四键值对剩余照明时间0-5999单位分钟。
*/
public Integer staPowerTime;
/**
* 第五键值对剩余电量百分比0-100
*/
public Integer staPowerPercent;
/**
* 第六键值对, 近电预警级别, 0-无预警1-弱预警2-中预警3-强预警4-非常强预警。
*/
public Integer staDetectResult;
}

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

@ -0,0 +1,85 @@
// package com.fuyuanshen.web.handler.mqtt;
//
// import cn.hutool.core.lang.Dict;
// import com.fuyuanshen.common.core.constant.GlobalConstants;
// import com.fuyuanshen.common.json.utils.JsonUtils;
// import com.fuyuanshen.common.redis.utils.RedisUtils;
// import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
// import com.fuyuanshen.global.mqtt.base.MqttRuleEngine;
// import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
// import lombok.extern.slf4j.Slf4j;
// import org.apache.commons.lang3.StringUtils;
// import org.springframework.beans.factory.annotation.Autowired;
// import org.springframework.messaging.Message;
// import org.springframework.messaging.MessageHandler;
// import org.springframework.messaging.MessageHeaders;
// import org.springframework.messaging.MessagingException;
// import org.springframework.stereotype.Service;
//
// import java.time.Duration;
// import java.util.Objects;
//
// import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
//
// /**
// * @author: 默苍璃
// * @date: 2025-09-1609:28
// */
// @Service
// @Slf4j
// public class DeviceAlrmMessageHandler implements MessageHandler {
//
// @Autowired
// private MqttRuleEngine ruleEngine;
//
// @Override
// public void handleMessage(Message<?> message) throws MessagingException {
//
// // 处理新类型的消息
// Object payload = message.getPayload();
// MessageHeaders headers = message.getHeaders();
// String receivedTopic = Objects.requireNonNull(headers.get("mqtt_receivedTopic")).toString();
//
// log.info("设备强制报警消息处理器 - MQTT payload= {} \n receivedTopic = {}", payload, receivedTopic);
//
// // 解析消息并执行相应逻辑
// Dict payloadDict = JsonUtils.parseMap(payload.toString());
// if (payloadDict != null) {
// // 根据主题或消息内容执行不同的处理逻辑
// processMessage(receivedTopic, payloadDict);
// }
// }
//
// private void processMessage(String topic, Dict payloadDict) {
// // 实现具体的业务逻辑
// // 可以根据不同的主题执行不同的操作
// if (topic.contains("newTopic")) {
// // 处理特定主题的消息
// handleNewTopicMessage(payloadDict);
// }
// }
//
// private void handleNewTopicMessage(Dict payloadDict) {
// // 实现新主题消息的具体处理逻辑
// String deviceImei = payloadDict.getStr("imei");
// if (StringUtils.isNotBlank(deviceImei)) {
// // 更新设备状态到Redis
// String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY +
// DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX;
// RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62));
//
// // 执行规则引擎
// MqttRuleContext context = new MqttRuleContext();
// context.setDeviceImei(deviceImei);
// context.setPayloadDict(payloadDict);
// // 设置命令类型
// context.setCommandType((byte) 0x02); // 根据实际需要设置命令类型
//
// boolean ruleExecuted = ruleEngine.executeRule(context);
// if (!ruleExecuted) {
// log.warn("未找到匹配的规则来处理新主题消息设备IMEI: {}", deviceImei);
// }
// }
// }
// }
//

View File

@ -1,131 +0,0 @@
package com.fuyuanshen.web.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fuyuanshen.app.domain.AppUser;
import com.fuyuanshen.app.domain.vo.AppUserVo;
import com.fuyuanshen.app.mapper.AppUserMapper;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.model.AppSmsRegisterBody;
import com.fuyuanshen.common.core.enums.UserType;
import com.fuyuanshen.common.core.exception.BadRequestException;
import com.fuyuanshen.common.core.exception.user.CaptchaException;
import com.fuyuanshen.common.core.exception.user.CaptchaExpireException;
import com.fuyuanshen.common.core.exception.user.UserException;
import com.fuyuanshen.common.core.utils.MessageUtils;
import com.fuyuanshen.common.core.utils.ServletUtils;
import com.fuyuanshen.common.core.utils.SpringUtils;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.log.event.LogininforEvent;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.tenant.helper.TenantHelper;
import com.fuyuanshen.common.web.config.properties.CaptchaProperties;
import com.fuyuanshen.system.mapper.SysUserMapper;
import com.fuyuanshen.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* 注册校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class AppRegisterService {
private final ISysUserService userService;
private final SysUserMapper userMapper;
private final CaptchaProperties captchaProperties;
private final AppUserMapper appUserMapper;
/**
* 注册
*/
public void register(AppSmsRegisterBody registerBody) {
String phoneNumber = registerBody.getPhoneNumber();
LambdaQueryWrapper<AppUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AppUser::getPhonenumber, phoneNumber);
wrapper.eq(AppUser::getUserType, UserType.APP_USER.getUserType());
AppUserVo appUserVo = appUserMapper.selectVoOne(wrapper);
if (appUserVo != null) {
throw new BadRequestException("该手机号已被注册");
}
String verificationCode = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phoneNumber);
if (verificationCode == null) {
throw new BadRequestException("验证码已过期");
}
if(!registerBody.getVerificationCode().equals(verificationCode)){
throw new BadRequestException("验证码错误");
}
String tenantId = registerBody.getTenantId();
String username = registerBody.getPhoneNumber();
String password = registerBody.getPassword();
AppUser appUser = new AppUser();
appUser.setUserName(username);
appUser.setNickName(username);
appUser.setPhonenumber(phoneNumber);
appUser.setPassword(password);
appUser.setUserType(UserType.APP_USER.getUserType());
appUser.setTenantId(tenantId);
appUser.setLoginIp(ServletUtils.getClientIP());
appUser.setStatus("0");
appUser.setDelFlag("0");
appUser.setCreateTime(new Date());
boolean exist = TenantHelper.dynamic(tenantId, () -> {
return appUserMapper.exists(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getUserName, appUser.getUserName()));
});
if (exist) {
throw new UserException("user.register.save.error", username);
}
appUserMapper.insert(appUser);
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
}

View File

@ -2,15 +2,18 @@ package com.fuyuanshen.web.service;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fuyuanshen.app.domain.AppDeviceBindRecord;
import com.fuyuanshen.app.domain.AppDeviceShare;
import com.fuyuanshen.app.domain.AppPersonnelInfo;
import com.fuyuanshen.app.domain.bo.AppDeviceShareBo;
import com.fuyuanshen.app.domain.vo.AppDeviceShareDetailVo;
import com.fuyuanshen.app.domain.vo.AppDeviceShareVo;
import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
import com.fuyuanshen.app.mapper.AppDeviceBindRecordMapper;
import com.fuyuanshen.app.mapper.AppDeviceShareMapper;
import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
@ -50,6 +53,8 @@ public class DeviceShareService {
private final AppPersonnelInfoMapper appPersonnelInfoMapper;
private final AppDeviceBindRecordMapper appDeviceBindRecordMapper;
public TableDataInfo<AppDeviceShareVo> queryPageList(AppDeviceShareBo bo, PageQuery pageQuery) {
Long userId = AppLoginHelper.getUserId();
bo.setCreateBy(userId);
@ -60,6 +65,16 @@ public class DeviceShareService {
return TableDataInfo.build(result);
}
public TableDataInfo<AppDeviceShareVo> queryWebPageList(AppDeviceShareBo bo, PageQuery pageQuery) {
// Long userId = AppLoginHelper.getUserId();
// bo.setCreateBy(userId);
Page<AppDeviceShareVo> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
Page<AppDeviceShareVo> result = appDeviceShareMapper.selectWebDeviceShareList(bo, page);
List<AppDeviceShareVo> records = result.getRecords();
records.forEach(DeviceShareService::buildDeviceStatus);
return TableDataInfo.build(result);
}
private static void buildDeviceStatus(AppDeviceShareVo item) {
// 设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
@ -175,7 +190,7 @@ public class DeviceShareService {
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
private boolean validateSmsCode(String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.DEVICE_SHARE_CODES_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
throw new ServiceException("验证码失效");
@ -184,16 +199,61 @@ public class DeviceShareService {
}
public int deviceShare(AppDeviceShareBo bo) {
boolean flag = validateSmsCode(AppLoginHelper.getTenantId(), bo.getPhonenumber(), bo.getSmsCode());
if (!flag) {
throw new ServiceException("验证码错误");
}
Device device = deviceMapper.selectById(bo.getDeviceId());
if (device == null) {
throw new ServiceException("设备不存在");
}
Long userId = AppLoginHelper.getUserId();
boolean flag = validateSmsCode( bo.getPhonenumber(), bo.getSmsCode());
if (!flag) {
throw new ServiceException("验证码错误");
}
LambdaQueryWrapper<AppDeviceBindRecord> qw = new LambdaQueryWrapper<>();
qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId());
AppDeviceBindRecord bindRecord = appDeviceBindRecordMapper.selectOne(qw);
if (bindRecord == null) {
throw new ServiceException("设备未绑定");
}
Long userId = bindRecord.getBindingUserId();
LambdaQueryWrapper<AppDeviceShare> lqw = new LambdaQueryWrapper<>();
lqw.eq(AppDeviceShare::getDeviceId, bo.getDeviceId());
lqw.eq(AppDeviceShare::getPhonenumber, bo.getPhonenumber());
Long count = appDeviceShareMapper.selectCount(lqw);
if (count > 0) {
UpdateWrapper<AppDeviceShare> uw = new UpdateWrapper<>();
uw.eq("device_id", bo.getDeviceId());
uw.eq("phonenumber", bo.getPhonenumber());
uw.set("permission", bo.getPermission());
uw.set("update_by", userId);
uw.set("update_time", new Date());
return appDeviceShareMapper.update(uw);
} else {
AppDeviceShare appDeviceShare = new AppDeviceShare();
appDeviceShare.setDeviceId(bo.getDeviceId());
appDeviceShare.setPhonenumber(bo.getPhonenumber());
appDeviceShare.setPermission(bo.getPermission());
appDeviceShare.setCreateBy(userId);
return appDeviceShareMapper.insert(appDeviceShare);
}
}
public int permission(AppDeviceShareBo bo) {
Device device = deviceMapper.selectById(bo.getDeviceId());
if (device == null) {
throw new ServiceException("设备不存在");
}
LambdaQueryWrapper<AppDeviceBindRecord> qw = new LambdaQueryWrapper<>();
qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId());
AppDeviceBindRecord bindRecord = appDeviceBindRecordMapper.selectOne(qw);
if (bindRecord == null) {
throw new ServiceException("设备未绑定");
}
Long userId = bindRecord.getBindingUserId();
LambdaQueryWrapper<AppDeviceShare> lqw = new LambdaQueryWrapper<>();
lqw.eq(AppDeviceShare::getDeviceId, bo.getDeviceId());
lqw.eq(AppDeviceShare::getPhonenumber, bo.getPhonenumber());

View File

@ -53,16 +53,21 @@ public interface WEBDeviceService extends IService<Device> {
* 设备操作记录
*
* @param deviceId
* @param startTime 开始时间
* @param endTime 结束时间
* @return
*/
List<DeviceLog> getOperationRecord(Long deviceId);
TableDataInfo<DeviceLog> getOperationRecord(Long deviceId, String startTime, String endTime, PageQuery pageQuery);
/**
* 设备告警记录
*
* @param deviceId
* @param startTime 开始时间
* @param endTime 结束时间
* @return
*/
List<DeviceAlarmVo> getAlarmRecord(Long deviceId);
TableDataInfo<DeviceAlarmVo> getAlarmRecord(Long deviceId, String startTime, String endTime, PageQuery pageQuery);
}

View File

@ -15,6 +15,7 @@ import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper;
import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.core.utils.*;
import com.fuyuanshen.common.redis.utils.RedisUtils;
@ -100,7 +101,6 @@ public class DeviceBJQBizService {
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", deviceId)
.eq("binding_user_id", AppLoginHelper.getUserId())
.set("send_msg", bo.getSendMsg());
deviceMapper.update(updateWrapper);
@ -109,7 +109,8 @@ public class DeviceBJQBizService {
log.info("发送信息设备发送信息失败:{}" ,deviceId);
throw new ServiceException("发送指令失败");
}
//发送消息
messageSending(device.getDeviceImei());
}
@ -172,10 +173,13 @@ public class DeviceBJQBizService {
}
//设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei()+ DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
if(StringUtils.isNotBlank(onlineStatus)){
vo.setOnlineStatus(1);
//设备在线状态
if("1".equals(onlineStatus)){
vo.setOnlineStatus(1);
}else if("2".equals(onlineStatus)){
vo.setOnlineStatus(2);
}else{
vo.setOnlineStatus(0);
vo.setOnlineStatus(0);
}
String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX);
// 获取电量
@ -251,8 +255,8 @@ public class DeviceBJQBizService {
ArrayList<Integer> intData = new ArrayList<>();
intData.add(2);
buildArr(convertHexToDecimal(unitName), intData);
buildArr(convertHexToDecimal(position), intData);
buildArr(convertHexToDecimal(name), intData);
buildArr(convertHexToDecimal(position), intData);
buildArr(convertHexToDecimal(id), intData);
intData.add(0);
intData.add(0);
@ -334,7 +338,10 @@ public class DeviceBJQBizService {
throw new ServiceException("设备不存在");
}
if(getDeviceStatus(device.getDeviceImei())){
throw new ServiceException(device.getDeviceName()+",设备已断开连接");
// throw new ServiceException(device.getDeviceName()+",设备已断开连接");
log.info(device.getDeviceName()+",设备已断开连接");
recordDeviceLog(device.getId(), device.getDeviceName(), "上传开机画面", device.getDeviceName()+",设备已断开连接", AppLoginHelper.getUserId());
return;
}
MultipartFile file = bo.getFile();
@ -519,16 +526,20 @@ public class DeviceBJQBizService {
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", deviceId)
.eq("binding_user_id", AppLoginHelper.getUserId())
.set("send_msg", bo.getSendMsg());
deviceMapper.update(updateWrapper);
recordDeviceLog(device.getId(), device.getDeviceName(), "发送告警信息", bo.getSendMsg(), AppLoginHelper.getUserId());
if("0".equals(bo.getInstructValue())){
recordDeviceLog(device.getId(), device.getDeviceName(), "解除告警信息", "关闭设备", AppLoginHelper.getUserId());
}else{
recordDeviceLog(device.getId(), device.getDeviceName(), "发送告警信息", bo.getSendMsg(), AppLoginHelper.getUserId());
}
} catch (Exception e) {
log.info("设备发送告警信息信息失败:{}" ,deviceId);
throw new ServiceException("设备发送告警信息信息失败");
}
messageSending(device.getDeviceImei());
}
} catch (Exception e){
e.printStackTrace();
throw new ServiceException("发送告警信息指令失败");
@ -536,8 +547,28 @@ public class DeviceBJQBizService {
return 1;
}
private void messageSending(String deviceImei){
String sendMessageIng = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceImei + ":messageSending";
RedisUtils.setCacheObject(sendMessageIng, "1", Duration.ofDays(1));
}
private boolean getDeviceStatus(String deviceImei) {
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
return StringUtils.isBlank(deviceOnlineStatusRedisKey);
return RedisUtils.getCacheObject(deviceOnlineStatusRedisKey) == null;
}
public void batchUploadLogo(AppDeviceLogoUploadDto bo) {
List<Long> deviceIds = bo.getDeviceIds();
MultipartFile file = bo.getFile();
if (deviceIds == null || deviceIds.isEmpty()) {
throw new ServiceException("请选择设备");
}
for (Long deviceId : deviceIds) {
AppDeviceLogoUploadDto dto = new AppDeviceLogoUploadDto();
dto.setDeviceId(deviceId);
dto.setFile(file);
uploadDeviceLogo(dto);
}
}
}

View File

@ -36,6 +36,7 @@ import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.web.service.device.status.base.DeviceStatusRule;
import com.fuyuanshen.web.service.device.status.base.RealTimeStatusEngine;
import com.google.common.primitives.Ints;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -89,9 +90,10 @@ public class DeviceBizService {
//设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
if(StringUtils.isNotBlank(onlineStatus)){
if("1".equals(onlineStatus)){
item.setOnlineStatus(1);
}else if("2".equals(onlineStatus)){
item.setOnlineStatus(2);
}else{
item.setOnlineStatus(0);
}
@ -126,16 +128,17 @@ public class DeviceBizService {
List<WebDeviceVo> records = result.getRecords();
if(records != null && !records.isEmpty()){
records.forEach(item -> {
if(item.getCommunicationMode()!=null && item.getCommunicationMode() == 0){
if(item.getCommunicationMode()!=null && Ints.asList(0, 2).contains(item.getCommunicationMode())){
//设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
if(StringUtils.isNotBlank(onlineStatus)){
item.setOnlineStatus(1);
}else{
item.setOnlineStatus(0);
}
// String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
// if("1".equals(onlineStatus)){
// item.setOnlineStatus(1);
// }else if("2".equals(onlineStatus)){
// item.setOnlineStatus(2);
// }else{
// item.setOnlineStatus(0);
// }
String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX+ item.getDeviceImei() + DEVICE_STATUS_KEY_PREFIX);
// 获取电量
if(StringUtils.isNotBlank(deviceStatus)){
@ -176,17 +179,18 @@ public class DeviceBizService {
}
Device device = devices.get(0);
if (device.getBindingStatus() != null && device.getBindingStatus() == BindingStatusEnum.BOUND.getCode()) {
throw new RuntimeException("设备已绑定");
throw new RuntimeException("设备已经被绑定");
}
QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>();
bindRecordQueryWrapper.eq("device_id", device.getId());
bindRecordQueryWrapper.eq("communication_mode", 0);
AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper);
if (appDeviceBindRecord != null) {
UpdateWrapper<AppDeviceBindRecord> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("device_id", device.getId())
.set("binding_status", BindingStatusEnum.BOUND.getCode())
.set("binding_user_id", userId)
.set("communication_mode", 0)
.set("update_time", new Date())
.set("binding_time", new Date());
return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper);
@ -195,6 +199,7 @@ public class DeviceBizService {
bindRecord.setDeviceId(device.getId());
bindRecord.setBindingUserId(userId);
bindRecord.setBindingTime(new Date());
bindRecord.setCommunicationMode(0);
bindRecord.setCreateBy(userId);
appDeviceBindRecordMapper.insert(bindRecord);
}
@ -224,6 +229,7 @@ public class DeviceBizService {
deviceUpdateWrapper.eq("device_id", device.getId())
.eq("binding_user_id", userId)
.set("binding_user_id", userId)
.set("communication_mode", 1)
.set("binding_time", new Date());
return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper);
} else {
@ -231,6 +237,7 @@ public class DeviceBizService {
bindRecord.setDeviceId(device.getId());
bindRecord.setBindingUserId(userId);
bindRecord.setBindingTime(new Date());
bindRecord.setCommunicationMode(1);
bindRecord.setCreateBy(userId);
appDeviceBindRecordMapper.insert(bindRecord);
}
@ -257,43 +264,74 @@ public class DeviceBizService {
if (device == null) {
throw new RuntimeException("请先将设备入库!!!");
}
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("id", device.getId())
.set("binding_user_id", null)
.set("binding_status", BindingStatusEnum.UNBOUND.getCode())
.set("binding_time", null);
deviceMapper.update(null, deviceUpdateWrapper);
if (userId == null) {
userId = AppLoginHelper.getUserId();
}
QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>();
bindRecordQueryWrapper.eq("device_id", device.getId());
// 设备端解绑 0:设备端解绑 1:web端解绑
if (type == 1) {
// type: 0 app,1 web
if (type == 0) {
QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>();
bindRecordQueryWrapper.eq("device_id", device.getId());
bindRecordQueryWrapper.eq("binding_user_id", userId);
Long count = appDeviceBindRecordMapper.selectCount(bindRecordQueryWrapper);
if (count == 0) {
throw new RuntimeException("请先绑定设备!!!");
}
if(count<2){
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("id", device.getId())
.set("binding_user_id", null)
.set("binding_status", BindingStatusEnum.UNBOUND.getCode())
.set("binding_time", null);
deviceMapper.update(null, deviceUpdateWrapper);
}
QueryWrapper<AppDeviceBindRecord> brqWrapper = new QueryWrapper<>();
brqWrapper.eq("device_id", device.getId());
brqWrapper.eq("binding_user_id", userId);
List<AppDeviceBindRecord> appDeviceBindRecordList = appDeviceBindRecordMapper.selectList(brqWrapper);
if (CollectionUtil.isNotEmpty(appDeviceBindRecordList)) {
appDeviceBindRecordList.forEach(appDeviceBindRecord ->
appDeviceBindRecordMapper.deleteById(appDeviceBindRecord.getId()));
}
AppUserVo appUserVo = appUserMapper.selectVoById(userId);
if(appUserVo != null){
QueryWrapper<AppDeviceShare> appDeviceShareQueryWrapper = new QueryWrapper<>();
appDeviceShareQueryWrapper.eq("device_id", device.getId());
appDeviceShareQueryWrapper.eq("phonenumber", appUserVo.getPhonenumber());
List<AppDeviceShare> appDeviceShareList = appDeviceShareMapper.selectList(appDeviceShareQueryWrapper);
if (CollectionUtil.isNotEmpty(appDeviceShareList)) {
appDeviceShareList.forEach(appDeviceShare ->
appDeviceShareMapper.deleteById(appDeviceShare.getId()));
}
}
}else{
QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>();
bindRecordQueryWrapper.eq("device_id", device.getId());
Long count = appDeviceBindRecordMapper.selectCount(bindRecordQueryWrapper);
if (count == 0) {
throw new RuntimeException("请先绑定设备!!!");
}
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("id", device.getId())
.set("binding_user_id", null)
.set("binding_status", BindingStatusEnum.UNBOUND.getCode())
.set("binding_time", null);
deviceMapper.update(null, deviceUpdateWrapper);
QueryWrapper<AppDeviceBindRecord> brqWrapper = new QueryWrapper<>();
brqWrapper.eq("device_id", device.getId());
List<AppDeviceBindRecord> appDeviceBindRecordList = appDeviceBindRecordMapper.selectList(brqWrapper);
if (CollectionUtil.isNotEmpty(appDeviceBindRecordList)) {
appDeviceBindRecordList.forEach(appDeviceBindRecord ->
appDeviceBindRecordMapper.deleteById(appDeviceBindRecord.getId()));
}
QueryWrapper<AppDeviceShare> appDeviceShareQueryWrapper = new QueryWrapper<>();
appDeviceShareQueryWrapper.eq("device_id", device.getId());
List<AppDeviceShare> appDeviceShareList = appDeviceShareMapper.selectList(appDeviceShareQueryWrapper);
if (CollectionUtil.isNotEmpty(appDeviceShareList)) {
appDeviceShareList.forEach(appDeviceShare ->
appDeviceShareMapper.deleteById(appDeviceShare.getId()));
}
}
// AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper);
// if (appDeviceBindRecord != null) {
// return appDeviceBindRecordMapper.deleteById(appDeviceBindRecord.getId());
// }
List<AppDeviceBindRecord> appDeviceBindRecordList = appDeviceBindRecordMapper.selectList(bindRecordQueryWrapper);
if (CollectionUtil.isNotEmpty(appDeviceBindRecordList)) {
appDeviceBindRecordList.forEach(appDeviceBindRecord ->
appDeviceBindRecordMapper.deleteById(appDeviceBindRecord.getId()));
}
AppUserVo appUserVo = appUserMapper.selectVoById(userId);
QueryWrapper<AppDeviceShare> appDeviceShareQueryWrapper = new QueryWrapper<>();
appDeviceShareQueryWrapper.eq("device_id", device.getId());
appDeviceShareQueryWrapper.eq("phonenumber", appUserVo.getPhonenumber());
List<AppDeviceShare> appDeviceShareList = appDeviceShareMapper.selectList(appDeviceShareQueryWrapper);
if (CollectionUtil.isNotEmpty(appDeviceShareList)) {
appDeviceShareList.forEach(appDeviceShare ->
appDeviceShareMapper.deleteById(appDeviceShare.getId()));
}
return 1;
}
@ -356,7 +394,7 @@ public class DeviceBizService {
String deviceImei = device.getDeviceImei();
String a = GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DEVICE_LOCATION_KEY_PREFIX + ":history";
Collection<String> list = RedisUtils.zRangeByScore(a, startTime, endTime);
Collection<String> list = RedisUtils.zRangeByScoreDesc(a, startTime, endTime);
if (CollectionUtil.isEmpty(list)) {
return null;
}
@ -367,6 +405,10 @@ public class DeviceBizService {
JSONObject jsonObject = JSONObject.parseObject(obj);
Long timestamp = jsonObject.getLong("timestamp");
LocalDate date = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()).toLocalDate();
String address = jsonObject.getString("address");
if(StringUtils.isBlank( address) || "[]".equals(address)){
continue;
}
if (map.containsKey(date.toString())) {
map.get(date.toString()).add(jsonObject);
} else {

View File

@ -0,0 +1,154 @@
package com.fuyuanshen.web.service.device;
import cn.hutool.core.collection.CollUtil;
import com.fuyuanshen.app.domain.AppBusinessFile;
import com.fuyuanshen.app.domain.AppOperationVideo;
import com.fuyuanshen.app.domain.bo.AppBusinessFileBo;
import com.fuyuanshen.app.domain.bo.AppOperationVideoBo;
import com.fuyuanshen.app.domain.dto.AppFileDto;
import com.fuyuanshen.app.service.IAppBusinessFileService;
import com.fuyuanshen.app.service.IAppOperationVideoService;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.service.ISysOssService;
import com.fuyuanshen.web.domain.vo.DeviceInfoVo;
import com.fuyuanshen.equipment.utils.FileHashUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 设备调试服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceDebugService {
private final ISysOssService sysOssService;
private final IAppBusinessFileService appBusinessFileService;
private final IAppOperationVideoService appOperationVideoService;
private final DeviceService deviceService;
/**
* 文件上传并添加文件信息哈希去重
* @param bo
* @return
* @throws IOException
*/
@Transactional(rollbackFor = Exception.class)
public Boolean addFileHash(AppFileDto bo,Boolean isBatch) throws IOException {
MultipartFile[] files = bo.getFiles();
if (files == null || files.length == 0) {
throw new ServiceException("请选择要上传的文件");
}
if (files.length > 5) {
throw new ServiceException("最多只能上传5个文件");
}
if (bo.getDeviceIds() == null || bo.getDeviceIds().length == 0) {
throw new ServiceException("请选择你要上传的设备");
}
Map<String, Long> hash2OssId = new LinkedHashMap<>(files.length);
for (MultipartFile file : files) {
// 1. 计算文件哈希
String hash = FileHashUtil.hash(file);
// 2. 先根据 hash 查库(秒传)
SysOssVo exist = sysOssService.selectByHash(hash);
Long ossId;
if (exist != null) {
// 2.1 已存在,直接复用
ossId = exist.getOssId();
hash2OssId.putIfAbsent(hash, ossId);
} else {
// 2.2 不存在,真正上传
SysOssVo upload = sysOssService.upload(file);
if (upload == null) {
return false;
}
ossId = upload.getOssId();
hash2OssId.putIfAbsent(hash, ossId);
// 2.3 把 hash 写回记录(供下次去重)
sysOssService.updateHashById(ossId, hash);
}
}
// 4. 组装业务中间表
List<AppBusinessFile> bizList = new ArrayList<>(bo.getDeviceIds().length * hash2OssId.size());
Long userId = AppLoginHelper.getUserId();
for (Long deviceId : bo.getDeviceIds()) {
for (Long ossId : hash2OssId.values()) {
// 3. 关联业务表
AppBusinessFile appFile = new AppBusinessFile();
appFile.setFileId(ossId);
appFile.setBusinessId(deviceId);
appFile.setFileType(bo.getFileType());
appFile.setCreateBy(userId);
bizList.add(appFile);
}
}
if (CollUtil.isEmpty(bizList)) { // 空集合直接返回
throw new ServiceException("请选择要上传的文件");
}
return appBusinessFileService.insertBatch(bizList,isBatch);
}
public Boolean addVideoList(AppOperationVideoBo bo){
if (bo.getVideoUrl().isEmpty()) {
throw new ServiceException("请输入视频地址");
}
if (bo.getDeviceIds() == null || bo.getDeviceIds().length == 0) {
throw new ServiceException("请选择你要上传的设备");
}
List<AppOperationVideo> bizList = new ArrayList<>(bo.getDeviceIds().length);
for (Long deviceId : bo.getDeviceIds()) {
AppOperationVideo appVideo = new AppOperationVideo();
appVideo.setVideoName(bo.getVideoName());
appVideo.setDeviceId(deviceId);
appVideo.setVideoUrl(bo.getVideoUrl());
bizList.add(appVideo);
}
if (CollUtil.isEmpty(bizList)) { // 空集合直接返回
throw new ServiceException("请选择要上传的视频");
}
return appOperationVideoService.insertBatch(bizList);
}
// 删除文件
public void delFile(List<Long> ids) {
// 3. 删除旧图片
if(ids != null){
appBusinessFileService.deleteWithValidByIds(ids, true);
}
}
/**
* 设备详情
* @param deviceId
* @return
*/
public DeviceInfoVo getDeviceInfo(Long deviceId) {
if(deviceId == null || deviceId <= 0L) {
throw new ServiceException("请选择设备");
}
DeviceInfoVo vo = new DeviceInfoVo();
var device = deviceService.getById(deviceId);
AppBusinessFileBo fileBo = new AppBusinessFileBo();
fileBo.setBusinessId(deviceId);
AppOperationVideoBo videoBo = new AppOperationVideoBo();
videoBo.setDeviceId(deviceId);
vo.setAppBusinessFileVoList(appBusinessFileService.queryAppFileList(fileBo));
vo.setAppOperationVideoVoList(appOperationVideoService.queryList(videoBo));
return vo;
}
}

View File

@ -1,15 +1,30 @@
package com.fuyuanshen.web.service.device;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.app.domain.AppPersonnelInfo;
import com.fuyuanshen.app.domain.AppPersonnelInfoRecords;
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo;
import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper;
import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.domain.model.AppLoginUser;
import com.fuyuanshen.common.core.domain.model.LoginUser;
import com.fuyuanshen.common.core.exception.BadRequestException;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.core.utils.ImageToCArrayConverter;
import com.fuyuanshen.common.core.utils.MapstructUtils;
@ -18,29 +33,49 @@ import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.equipment.domain.*;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.equipment.domain.form.DeviceForm;
import com.fuyuanshen.equipment.domain.query.DeviceTypeQueryCriteria;
import com.fuyuanshen.equipment.enums.DeviceActiveStatusEnum;
import com.fuyuanshen.equipment.enums.LightModeEnum;
import com.fuyuanshen.equipment.mapper.DeviceLogMapper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeGrantsMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.equipment.service.DeviceAssignmentsService;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import com.fuyuanshen.global.mqtt.base.MqttXinghanJson;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.domain.vo.SysRoleVo;
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.enums.AlarmTypeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.buildArr;
import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.generateFixedBitmapData;
import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_BOOT_LOGO_KEY_PREFIX;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
import static com.fuyuanshen.equipment.service.impl.DeviceServiceImpl.USER_ID_SEPARATOR;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
@Slf4j
@Service
@ -52,6 +87,12 @@ public class DeviceXinghanBizService {
private final DeviceTypeMapper deviceTypeMapper;
private final MqttGateway mqttGateway;
private final DeviceLogMapper deviceLogMapper;
private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper;
private final IDeviceAlarmService deviceAlarmService;
private final DeviceTypeGrantsMapper deviceTypeGrantsMapper;
private final DeviceAssignmentsService deviceAssignmentsService;
@Autowired
private ObjectMapper objectMapper;
/**
* 所有档位的描述表
@ -62,7 +103,7 @@ public class DeviceXinghanBizService {
"ins_DetectGrade", Map.of(1, "低档", 2, "中档", 3, "高档"),
"ins_LightGrade", Map.of(1, "强光", 2, "弱光"),
"ins_SOSGrade", Map.of(1, "爆闪模式", 2, "红蓝模式"),
"ins_ShakeBit", Map.of(0, "未静止报警", 1, "正在静止报警")
"ins_ShakeBit", Map.of(1, "开启报警")
// 再加 4、5、6…… 档,直接往 Map 里塞即可
);
@ -77,31 +118,107 @@ public class DeviceXinghanBizService {
/**
* 设置静电预警档位
*/
public void upDetectGradeSettings(DeviceInstructDto dto) {
public void upDetectGradeSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_DetectGrade","静电预警档位");
}
/**
* 设置照明档位
*/
public void upLightGradeSettings(DeviceInstructDto dto) {
public void upLightGradeSettings(DeviceXinghanInstructDto dto) {
sendCommand(dto, "ins_LightGrade","照明档位");
}
/**
* 设置SOS档位
*/
public void upSOSGradeSettings(DeviceInstructDto dto) {
sendCommand(dto, "ins_SOSGrade","SOS档位");
public void upSOSGradeSettings(DeviceXinghanInstructDto dto) {
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","强制报警");
}
public DeviceXinghanDetailVo getInfo(Long id) {
Device device = deviceMapper.selectById(id);
if (device == null) {
throw new RuntimeException("请先将设备入库!!!");
}
DeviceXinghanDetailVo vo = new DeviceXinghanDetailVo();
vo.setDeviceId(device.getId());
vo.setDeviceName(device.getDeviceName());
vo.setDevicePic(device.getDevicePic());
vo.setDeviceImei(device.getDeviceImei());
vo.setDeviceMac(device.getDeviceMac());
vo.setDeviceStatus(device.getDeviceStatus());
DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType());
if (deviceType != null) {
vo.setCommunicationMode(Integer.valueOf(deviceType.getCommunicationMode()));
vo.setTypeName(deviceType.getTypeName());
}
vo.setBluetoothName(device.getBluetoothName());
QueryWrapper<AppPersonnelInfo> qw = new QueryWrapper<AppPersonnelInfo>()
.eq("device_id", device.getId());
AppPersonnelInfo appPersonnelInfo = appPersonnelInfoMapper.selectOne(qw);
if (appPersonnelInfo != null) {
AppPersonnelInfoVo personnelInfoVo = MapstructUtils.convert(appPersonnelInfo, AppPersonnelInfoVo.class);
vo.setPersonnelInfo(personnelInfoVo);
}
//设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei()+ DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
if(StringUtils.isNotBlank(onlineStatus)){
vo.setOnlineStatus(1);
}else{
vo.setOnlineStatus(0);
}
String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX);
// 获取设备上报的设备状态
if(StringUtils.isNotBlank(deviceStatus)){
try {
MqttXinghanJson deviceJson = objectMapper.readValue(deviceStatus, MqttXinghanJson.class);
vo.setStaLightGrade(deviceJson.getStaLightGrade());
vo.setStaPowerPercent(deviceJson.getStaPowerPercent());
vo.setStaSOSGrade(deviceJson.getStaSOSGrade());
vo.setStaDetectGrade(deviceJson.getStaDetectGrade());
vo.setStaPowerTime(deviceJson.getStaPowerTime());
} catch (Exception e) {
throw new IllegalArgumentException("设备状态缓存格式非法", e);
}
}else{
vo.setStaPowerPercent(0);
}
// 获取经度纬度
String locationKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX;
String locationInfo = RedisUtils.getCacheObject(locationKey);
if(StringUtils.isNotBlank(locationInfo)){
JSONObject jsonObject = JSONObject.parseObject(locationInfo);
vo.setLongitude(jsonObject.get("longitude").toString());
vo.setLatitude(jsonObject.get("latitude").toString());
vo.setAddress((String)jsonObject.get("address"));
}
return vo;
}
/**
* 上传设备logo
*/
@ -143,29 +260,94 @@ public class DeviceXinghanBizService {
}
}
/**
* 批量上传设备logo
*/
@Transactional(rollbackFor = Exception.class)
public void uploadDeviceLogoBatch(DeviceDebugLogoUploadDto batchDto) {
if (CollectionUtils.isEmpty(batchDto.getDeviceIds())) {
throw new ServiceException("设备列表为空");
}
// 1. 一次性把设备查出来N -> 1
QueryWrapper<Device> query = new QueryWrapper<>();
query.in("id", batchDto.getDeviceIds());
List<Device> devices = deviceMapper.selectList(query);
if (devices.size() != batchDto.getDeviceIds().size()) {
throw new ServiceException("部分设备不存在");
}
// 2. 图片只转换一次160*80 固定尺寸)
byte[] largeData;
try {
largeData = ImageToCArrayConverter.convertImageToCArray(
batchDto.getFile().getInputStream(), 160, 80, 25600);
} catch (IOException e) {
throw new ServiceException("图片解析失败");
}
int[] picArray = convertHexToDecimal(largeData);
// 3. 过滤离线设备 & 组装指令
List<Device> onlineDevices = devices.stream()
.filter(d -> !isDeviceOffline(d.getDeviceImei()))
.toList();
onlineDevices.forEach(d -> {
String redisKey = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + d.getDeviceImei() + DEVICE_BOOT_LOGO_KEY_PREFIX;
// 如果 Redis 里已存在,直接跳过
if (RedisUtils.getCacheObject(redisKey) != null) {
return; // 跳过本次循环
}
RedisUtils.setCacheObject(
redisKey,
Arrays.toString(picArray),
Duration.ofSeconds(5 * 60L));
// 3.2 MQTT 下发
Map<String, Object> payload =
Collections.singletonMap("ins_PicTrans", Collections.singletonList(0));
String topic = MqttConstants.GLOBAL_PUB_KEY + d.getDeviceImei();
String json = JsonUtils.toJsonString(payload);
try {
mqttGateway.sendMsgToMqtt(topic, 1, json);
} catch (Exception e) {
log.error("上传开机画面失败, topic={}, payload={}", topic, json, e);
throw new ServiceException("上传LOGO失败" + e.getMessage());
}
recordDeviceLog(d.getId(), d.getDeviceName(), "上传开机画面", "上传开机画面", AppLoginHelper.getUserId());
});
}
/**
* 人员登记
* @param bo
*/
@Transactional(rollbackFor = Exception.class) // 1. 事务
public boolean registerPersonInfo(AppPersonnelInfoBo bo) {
Long deviceId = bo.getDeviceId();
Device deviceObj = deviceMapper.selectById(deviceId);
if (deviceObj == null) {
throw new RuntimeException("请先将设备入库!!!");
}
if (deviceObj.getDeviceImei() == null) {
return saveOrUpdatePersonnelInfo(bo, deviceId);
}
if (isDeviceOffline(deviceObj.getDeviceImei())) {
throw new ServiceException("设备已断开连接:" + deviceObj.getDeviceName());
}
QueryWrapper<AppPersonnelInfo> qw = new QueryWrapper<AppPersonnelInfo>()
.eq("device_id", deviceId);
List<AppPersonnelInfoVo> appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw);
List<String> list = new ArrayList<>();
list.add(bo.getUnitName());
list.add(bo.getName());
list.add(bo.getPosition());
list.add(bo.getCode());
RedisUtils.setCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", list);
String key = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data";
RedisUtils.deleteObject(key); // 先整体删掉
RedisUtils.setCacheList(key, list); // 再重新写入
RedisUtils.expire(key, Duration.ofSeconds(5*60));
Map<String, Object> payload = Map.of("ins_TexTrans",
Collections.singletonList(0));
@ -181,60 +363,307 @@ public class DeviceXinghanBizService {
log.info("发送人员信息登记到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei(), bo);
recordDeviceLog(deviceId, deviceObj.getDeviceName(), "人员信息登记", JSON.toJSONString(bo), AppLoginHelper.getUserId());
if (ObjectUtils.length(appPersonnelInfoVos) == 0) {
AppPersonnelInfo appPersonnelInfo = MapstructUtils.convert(bo, AppPersonnelInfo.class);
return appPersonnelInfoMapper.insertOrUpdate(appPersonnelInfo);
} else {
UpdateWrapper<AppPersonnelInfo> uw = new UpdateWrapper<>();
uw.eq("device_id", deviceId)
.set("name", bo.getName())
.set("position", bo.getPosition())
.set("unit_name", bo.getUnitName())
.set("code", bo.getCode());
return appPersonnelInfoMapper.update(null, uw) > 0;
}
return saveOrUpdatePersonnelInfo(bo, deviceId);
}
/**
* 仅内部调用,已处于 @Transactional 内
*/
private Boolean saveOrUpdatePersonnelInfo(AppPersonnelInfoBo bo, Long deviceId) {
// 1. 先查主表有没有
AppPersonnelInfo main = appPersonnelInfoMapper.selectOne(
Wrappers.<AppPersonnelInfo>lambdaQuery()
.eq(AppPersonnelInfo::getDeviceId, deviceId));
if (main == null) {
/* ====== 新增场景 ====== */
main = MapstructUtils.convert(bo, AppPersonnelInfo.class);
main.setDeviceId(deviceId);
// 1.1 主表插入 -> 主键回写
appPersonnelInfoMapper.insert(main); // 执行后 main.getId() 一定有值
} else {
/* ====== 更新场景 ====== */
// 1.2 把 bo 的新值刷到主表
appPersonnelInfoMapper.update(null,
Wrappers.<AppPersonnelInfo>update()
.eq("device_id", deviceId)
.set("name", bo.getName())
.set("position", bo.getPosition())
.set("unit_name", bo.getUnitName())
.set("code", bo.getCode())
.set("update_time", new Date()));
}
// 2. 无论新增/更新,都插入一条履历
AppPersonnelInfoRecords record = new AppPersonnelInfoRecords();
// 只拷业务字段不拷时间、id、操作人
BeanUtil.copyProperties(main, record, "id", "createTime", "updateTime", "createBy", "updateBy");
record.setId(null); // 让自增
record.setPersonnelId(main.getId()); // 关键:主键已回写,不会为 null
appPersonnelInfoRecordsMapper.insert(record);
return true;
}
/**
* 发送报警信息
* @param bo
* @return
*/
public int sendAlarmMessage(AppDeviceSendMsgBo bo) {
List<Long> deviceIds = bo.getDeviceIds();
// 1. 简化非空检查和抛出异常
if (deviceIds == null || deviceIds.isEmpty()) {
throw new ServiceException("请选择设备");
}
if(!StringUtils.isNotEmpty(bo.getSendMsg()))
{
throw new ServiceException("请输入发送内容");
}
QueryWrapper<Device> queryWrapper = new QueryWrapper<>();
// 使用 in 语句根据id集合查询
queryWrapper.in("id", deviceIds);
// 2. 将批量查询设备,减少数据库交互次数
List<Device> devices = deviceMapper.selectList(queryWrapper);
if (devices.size() != deviceIds.size()) {
// 如果查询回来的设备数量不一致,说明有设备不存在,此处可以优化为更详细的提示
throw new ServiceException("部分设备不存在");
}
try {
for (Device device : devices) {
String deviceImei = device.getDeviceImei();
String deviceName = device.getDeviceName();
// 3. 在循环中进行设备状态检查,快速失败
if (isDeviceOffline(deviceImei)) {
// 如果设备离线,可以选择继续处理下一个设备,或者抛出异常。这里选择抛出异常。
throw new ServiceException(deviceName + ", 设备已断开连接");
}
// 4. 将Redis和MQTT操作封装在一个方法中提高可读性
sendSingleAlarmMessage(device, bo.getSendMsg());
// 5. 批量更新设备状态,提高效率
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", device.getId())
.eq("binding_user_id", AppLoginHelper.getUserId())
.set("send_msg", bo.getSendMsg());
deviceMapper.update(updateWrapper);
// 6. 记录操作日志
recordDeviceLog(device.getId(), deviceName, "发送紧急通知", bo.getSendMsg(), AppLoginHelper.getUserId());
}
} catch (ServiceException e) {
// 捕获并重新抛出自定义异常,避免内层异常被外层泛化捕获
log.error("发送告警信息指令失败: {}", e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("发送告警信息指令发生未知错误", e);
throw new ServiceException("发送告警信息指令失败");
}
return 1;
}
/* ---------------------------------- 私有通用方法 ---------------------------------- */
private void sendCommand(DeviceInstructDto dto,
String payloadKey,String deviceAction) {
long deviceId = dto.getDeviceId();
Device device = deviceMapper.selectById(deviceId);
if (device == null) {
throw new ServiceException("设备不存在");
}
if (isDeviceOffline(device.getDeviceImei())) {
throw new ServiceException("设备已断开连接:" + device.getDeviceName());
}
/**
* 封装单个设备发送告警信息的逻辑
*/
private void sendSingleAlarmMessage(Device device, String message) {
String deviceImei = device.getDeviceImei();
int msgLen = message.length() / 2;
StringBuilder msgBuilder = new StringBuilder(message);
msgBuilder.insert(msgLen, '|');
// 缓存告警消息到Redis
RedisUtils.setCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_ALARM_MESSAGE_KEY_PREFIX, msgBuilder.toString(), Duration.ofSeconds(5 * 60L));
Integer value = Integer.parseInt(dto.getInstructValue());
Map<String, Object> payload = Map.of(payloadKey,
Collections.singletonList(value));
String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei();
// 构建并发送MQTT消息
Map<String, Object> payload = Map.of("ins_BreakNews", Collections.singletonList(0));
String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei;
String json = JsonUtils.toJsonString(payload);
try {
mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("发送紧急通知成功 => topic:{}, payload:{}", topic, json);
} catch (Exception e) {
log.error("发送紧急通知失败, topic={}, payload={}", topic, json, e);
throw new ServiceException("发送紧急通知失败:" + e.getMessage());
}
}
/**
* 发送设备控制指令
*
* @param dto 设备指令数据传输对象包含设备ID和指令值等信息
* @param payloadKey 指令负载数据的键名
* @param deviceAction 设备操作类型描述
*/
private void sendCommand(DeviceXinghanInstructDto dto, String payloadKey, String deviceAction) {
long deviceId = dto.getDeviceId();
// 1. 使用Optional简化空值检查使代码更简洁
Device device = Optional.ofNullable(deviceMapper.selectById(deviceId))
.orElseThrow(() -> new ServiceException("设备不存在"));
String deviceImei = device.getDeviceImei();
String deviceName = device.getDeviceName();
// 2. 提前进行设备状态检查,逻辑更清晰
if (isDeviceOffline(deviceImei)) {
throw new ServiceException("设备已断开连接:" + deviceName);
}
// 3. 统一处理类型转换异常避免在业务逻辑中混杂try-catch
int value;
try {
value = Integer.parseInt(dto.getInstructValue());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("指令值格式不正确,必须为整数。", e);
}
// 4. 使用Map.of()或Map.ofEntries()创建不可变Map更简洁且线程安全
Map<String, List<Integer>> payload = Map.of(payloadKey, List.of(value));
String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei;
String json = JsonUtils.toJsonString(payload);
try {
mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("发送指令成功 => topic:{}, payload:{}", topic, json);
} catch (Exception e) {
log.error("发送指令失败, topic={}, payload={}", topic, json, e);
throw new ServiceException("发送指令失败:" + e.getMessage());
}
log.info("发送指令成功 => topic:{}, payload:{}", topic, json);
// 5. 将日志记录和描述解析放在try-catch块之外确保无论是否成功发送指令都能执行
String content = resolveGradeDesc("ins_DetectGrade", value);
recordDeviceLog(device.getId(),
device.getDeviceName(),
deviceName,
deviceAction,
content,
AppLoginHelper.getUserId());
// 6. 新建报警信息
createAlarm(device.getId(),deviceImei,payloadKey,value);
}
private boolean isDeviceOffline(String imei) {
// 原方法名语义相反,这里取反,使含义更清晰
return getDeviceStatus(imei);
/**
* 批量发送设备控制指令
*
* @param dto 设备ID列表
* @param payloadKey 指令负载数据的键名
* @param deviceAction 设备操作类型描述
*/
@Transactional(rollbackFor = Exception.class) // 1. 事务注解
public void sendCommandBatch(DeviceXinghanInstructDto dto, String payloadKey, String deviceAction) {
List<String> errorMessages = Collections.synchronizedList(new ArrayList<>());
int value;
try {
value = Integer.parseInt(dto.getInstructValue());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("指令值格式不正确,必须为整数。", e);
}
Map<String, List<Integer>> payload = Map.of(payloadKey, List.of(value));
// 一次性查询所有设备信息
List<Device> devices = deviceMapper.selectList(
new QueryWrapper<Device>().lambda().in(Device::getId, dto.getDeviceIds())
);
// 日志信息
String contentText = resolveGradeDesc(payloadKey, value);
List<DeviceLog> logs = new ArrayList<>();
try {
for (Device device : devices) {
String deviceImei = device.getDeviceImei();
String deviceName = device.getDeviceName();
// 2. 提前进行设备状态检查,逻辑更清晰
if (isDeviceOffline(deviceImei)) {
throw new ServiceException("设备已断开连接:" + deviceName);
}
String topic = MqttConstants.GLOBAL_PUB_KEY + deviceImei;
String json = JsonUtils.toJsonString(payload);
mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("发送指令成功 => topic:{}, payload:{}", topic, json);
// 创建设备日志实体
DeviceLog deviceLog = new DeviceLog();
deviceLog.setDeviceId(device.getId());
deviceLog.setDeviceAction(deviceAction);
deviceLog.setContent(contentText);
deviceLog.setCreateBy(AppLoginHelper.getUserId());
deviceLog.setDeviceName(deviceName);
deviceLog.setCreateTime(new Date());
logs.add(deviceLog);
createAlarm(device.getId(), deviceImei, payloadKey, value);
}
deviceLogMapper.insertBatch(logs);
} catch (ServiceException e) {
// 捕获并重新抛出自定义异常,避免内层异常被外层泛化捕获
log.error("批量发送指令失败: {}", e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("批量发送指令发生未知错误", e);
throw new ServiceException("批量发送指令失败");
}
}
/**
* 检查设备是否离线
*/
// private boolean isDeviceOffline(String 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 = AlarmTypeEnum.SOS;
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());
}
/**
@ -261,9 +690,81 @@ public class DeviceXinghanBizService {
}
}
private boolean getDeviceStatus(String deviceImei) {
private boolean isDeviceOffline(String deviceImei) {
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
return StringUtils.isBlank(deviceOnlineStatusRedisKey);
return RedisUtils.getCacheObject(deviceOnlineStatusRedisKey)==null;
}
public List<DeviceType> queryDeviceTypes() {
DeviceTypeQueryCriteria criteria = new DeviceTypeQueryCriteria();
return deviceTypeMapper.findAll(criteria);
}
// @Log("新增设备")
public void addDevice(DeviceForm deviceForm) {
if (deviceForm.getDeviceMac() != null && deviceForm.getBluetoothName() == null) {
throw new BadRequestException("请填写蓝牙名称!!!");
}
Device device1 = deviceMapper.selectOne(new QueryWrapper<Device>().eq("device_mac", deviceForm.getDeviceMac()));
if (device1 != null) {
throw new BadRequestException("设备MAC已存在");
}
Device device2 = deviceMapper.selectOne(new QueryWrapper<Device>().eq("device_imei", deviceForm.getDeviceImei()));
if (device2 != null) {
throw new BadRequestException("设备IMEI已存在");
}
DeviceTypeQueryCriteria queryCriteria = new DeviceTypeQueryCriteria();
queryCriteria.setDeviceTypeId(deviceForm.getDeviceType());
queryCriteria.setCustomerId(LoginHelper.getUserId());
DeviceTypeGrants typeGrants = deviceTypeGrantsMapper.selectById(queryCriteria.getDeviceTypeId());
if (typeGrants == null) {
throw new ServiceException("设备类型不存在!!!");
}
DeviceType deviceTypes = deviceTypeMapper.selectById(typeGrants.getDeviceTypeId());
if (deviceTypes == null) {
throw new ServiceException("设备类型不存在!!!");
}
// 转换对象并插入数据库
Device device = new Device();
BeanUtil.copyProperties(deviceForm, device, true);
device.setDeviceNo(createDeviceNo());
AppLoginUser loginUser = AppLoginHelper.getLoginUser();
device.setCurrentOwnerId(loginUser.getUserId());
device.setOriginalOwnerId(loginUser.getUserId());
device.setCreateByName(loginUser.getNickname());
device.setTypeName(deviceTypes.getTypeName());
device.setDeviceType(deviceTypes.getId());
if (device.getDeviceImei() != null) {
device.setPubTopic("A/" + device.getDeviceImei());
device.setSubTopic("B/" + device.getDeviceImei());
}
// 0 未绑定
device.setBindingStatus(0);
deviceMapper.insert(device);
// 新增设备类型记录
DeviceAssignments assignments = new DeviceAssignments();
assignments.setDeviceId(device.getId());
assignments.setAssignedAt(LocalDateTime.now());
// 分配者
assignments.setAssignerId(loginUser.getUserId());
assignments.setAssignerName(loginUser.getUsername());
// 接收者
assignments.setAssigneeId(loginUser.getUserId());
assignments.setActive(DeviceActiveStatusEnum.ACTIVE.getCode());
String lever = USER_ID_SEPARATOR + loginUser.getUserId();
assignments.setLever(lever);
deviceAssignmentsService.save(assignments);
}
private String createDeviceNo() {
String uuidStr = UUID.fastUUID().toString(); // 获取带 - 的标准格式字符串
return uuidStr.replaceAll("-", "");
}
}

View File

@ -3,6 +3,7 @@ package com.fuyuanshen.web.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fuyuanshen.app.domain.AppUser;
import com.fuyuanshen.app.domain.vo.AppUserVo;
@ -12,6 +13,8 @@ import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.constant.SystemConstants;
import com.fuyuanshen.common.core.domain.model.AppLoginUser;
import com.fuyuanshen.common.core.domain.model.AppPasswordLoginBody;
import com.fuyuanshen.common.core.domain.model.LoginUser;
import com.fuyuanshen.common.core.domain.model.PasswordLoginBody;
import com.fuyuanshen.common.core.enums.LoginType;
import com.fuyuanshen.common.core.enums.UserType;
@ -27,15 +30,19 @@ import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.common.tenant.helper.TenantHelper;
import com.fuyuanshen.common.web.config.properties.CaptchaProperties;
import com.fuyuanshen.system.domain.SysUser;
import com.fuyuanshen.system.domain.vo.SysClientVo;
import com.fuyuanshen.system.domain.vo.SysUserVo;
import com.fuyuanshen.system.mapper.SysUserMapper;
import com.fuyuanshen.web.domain.vo.LoginVo;
import com.fuyuanshen.web.service.IAuthStrategy;
import com.fuyuanshen.web.service.SysLoginService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 密码认证策略
* APP密码认证策略
*
* @author Michelle.Chung
*/
@ -47,27 +54,22 @@ public class AppPasswordAuthStrategy implements IAuthStrategy {
private final CaptchaProperties captchaProperties;
private final AppLoginService loginService;
private final AppUserMapper appUserMapper;
@Override
public LoginVo login(String body, SysClientVo client) {
PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
AppPasswordLoginBody loginBody = JsonUtils.parseObject(body, AppPasswordLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String username = loginBody.getUsername();
String password = loginBody.getPassword();
String code = loginBody.getCode();
String uuid = loginBody.getUuid();
// boolean captchaEnabled = captchaProperties.getEnable();
// // 验证码开关
// if (captchaEnabled) {
// validateCaptcha(tenantId, username, code, uuid);
// }
AppLoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
AppUserVo user = loadUserByUsername(username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !password.equals(user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
@ -109,12 +111,23 @@ public class AppPasswordAuthStrategy implements IAuthStrategy {
}
private AppUserVo loadUserByUsername(String username) {
// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
// if (ObjectUtil.isNull(user)) {
// log.info("登录用户:{} 不存在.", username);
// throw new UserException("user.not.exists", username);
// } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
// log.info("登录用户:{} 已被停用.", username);
// throw new UserException("user.blocked", username);
// }
// return user;
AppUserVo user = appUserMapper.selectVoOne(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getUserName, username)
.eq(AppUser::getPhonenumber, username)
.eq(AppUser::getUserType, UserType.APP_USER.getUserType()));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);

View File

@ -1,16 +1,20 @@
package com.fuyuanshen.web.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fuyuanshen.app.domain.AppDeviceBindRecord;
import com.fuyuanshen.app.domain.AppDeviceShare;
import com.fuyuanshen.app.domain.AppPersonnelInfoRecords;
import com.fuyuanshen.app.domain.vo.AppDeviceShareVo;
import com.fuyuanshen.app.mapper.AppDeviceBindRecordMapper;
import com.fuyuanshen.app.mapper.AppDeviceShareMapper;
import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceAlarm;
import com.fuyuanshen.equipment.domain.DeviceAssignments;
@ -23,7 +27,6 @@ import com.fuyuanshen.equipment.mapper.DeviceAssignmentsMapper;
import com.fuyuanshen.equipment.mapper.DeviceLogMapper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.web.service.WEBDeviceService;
import com.fuyuanshen.web.service.device.DeviceBizService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -129,11 +132,19 @@ public class WEBDeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impl
* @return
*/
@Override
public List<DeviceLog> getOperationRecord(Long deviceId) {
List<DeviceLog> logList = deviceLogMapper.selectList(
new QueryWrapper<DeviceLog>().eq("device_id", deviceId)
.orderByDesc("create_time"));
return logList;
public TableDataInfo<DeviceLog> getOperationRecord(Long deviceId, String startTime, String endTime, PageQuery pageQuery) {
Page<DeviceLog> page = pageQuery.build();
QueryWrapper<DeviceLog> queryWrapper = new QueryWrapper<DeviceLog>().eq("device_id", deviceId);
if (StrUtil.isNotEmpty(startTime)) {
queryWrapper.ge("create_time", startTime);
}
if (StrUtil.isNotEmpty(endTime)) {
queryWrapper.le("create_time", endTime);
}
queryWrapper.orderByDesc("create_time");
IPage<DeviceLog> logList = deviceLogMapper.selectPage(page, queryWrapper);
return TableDataInfo.build(logList);
}
@ -144,13 +155,23 @@ public class WEBDeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impl
* @return
*/
@Override
public List<DeviceAlarmVo> getAlarmRecord(Long deviceId) {
List<DeviceAlarm> alarmList = deviceAlarmMapper.selectList(
new QueryWrapper<DeviceAlarm>().eq("device_id", deviceId)
.orderByDesc("create_time"));
List<DeviceAlarmVo> deviceAlarmVoList = BeanUtil.copyToList(alarmList, DeviceAlarmVo.class);
return deviceAlarmVoList;
public TableDataInfo getAlarmRecord(Long deviceId, String startTime, String endTime, PageQuery pageQuery) {
Page<DeviceAlarm> page = pageQuery.build();
QueryWrapper<DeviceAlarm> queryWrapper = new QueryWrapper<DeviceAlarm>().eq("device_id", deviceId);
if (StrUtil.isNotEmpty(startTime)) {
queryWrapper.ge("start_time", startTime);
}
if (StrUtil.isNotEmpty(endTime)) {
queryWrapper.le("start_time", endTime);
}
queryWrapper.orderByDesc("start_time");
IPage<DeviceAlarm> alarmPage = deviceAlarmMapper.selectPage(page, queryWrapper);
// List<DeviceAlarmVo> deviceAlarmVoList = BeanUtil.copyToList(alarmPage.getRecords(), DeviceAlarmVo.class);
return TableDataInfo.build(alarmPage);
}
}

View File

@ -0,0 +1,194 @@
package com.fuyuanshen.web.util;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
/**
* 视频处理工具类
*/
@Slf4j
@Component
public class VideoProcessUtil {
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/**
* 创建临时视频文件
*/
public File createTempVideoFile(MultipartFile file) throws Exception {
File tempFile = Files.createTempFile("upload-", ".mp4").toFile();
file.transferTo(tempFile);
log.debug("创建临时视频文件: {}", tempFile.getAbsolutePath());
return tempFile;
}
/**
* 处理视频并转换为Hex字符串列表
*/
public List<String> processVideoToHex(File videoFile, int frameRate, int duration, int width, int height) throws Exception {
// 1. 提取视频帧
List<BufferedImage> frames = extractFramesFromVideo(videoFile, frameRate, duration, width, height);
// 2. 转换为RGB565格式
byte[] binaryData = convertFramesToRGB565(frames);
// 3. 转换为Hex字符串列表
return bytesToHexList(binaryData);
}
/**
* 从视频中提取帧
*/
private List<BufferedImage> extractFramesFromVideo(File videoFile, int frameRate, int duration, int width, int height) throws Exception {
List<BufferedImage> frames = new ArrayList<>();
int totalFramesToExtract = frameRate * duration;
try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoFile)) {
grabber.start();
long totalFramesInVideo = grabber.getLengthInFrames();
int fps = (int) Math.round(grabber.getFrameRate());
if (fps <= 0) fps = 30;
double durationSeconds = (double) totalFramesInVideo / fps;
if (durationSeconds < duration) {
throw new IllegalArgumentException("视频太短,至少需要 " + duration + "");
}
double frameInterval = (double) totalFramesInVideo / totalFramesToExtract;
for (int i = 0; i < totalFramesToExtract; i++) {
int targetFrameNumber = (int) Math.round(i * frameInterval);
if (targetFrameNumber >= totalFramesInVideo) {
throw new IllegalArgumentException("目标帧超出范围: " + targetFrameNumber);
}
grabber.setFrameNumber(targetFrameNumber);
Frame frame = grabber.grab();
if (frame != null && frame.image != null) {
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
frames.add(cropImage(bufferedImage, width, height));
} else {
throw new IllegalArgumentException("无法获取第 " + targetFrameNumber + "");
}
}
grabber.stop();
}
log.debug("从视频中提取了 {} 帧", frames.size());
return frames;
}
/**
* 将所有帧转换为 RGB565 格式字节数组
*/
private byte[] convertFramesToRGB565(List<BufferedImage> frames) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
for (BufferedImage image : frames) {
byte[] rgb565Bytes = convertToRGB565(image);
byteArrayOutputStream.write(rgb565Bytes);
}
byte[] result = byteArrayOutputStream.toByteArray();
log.debug("转换RGB565数据完成总字节数: {}", result.length);
return result;
}
/**
* 将字节数组转换为Hex字符串列表
*/
private List<String> bytesToHexList(byte[] bytes) {
List<String> hexList = new ArrayList<>();
for (byte b : bytes) {
int value = b & 0xFF;
char high = HEX_ARRAY[value >>> 4];
char low = HEX_ARRAY[value & 0x0F];
hexList.add(String.valueOf(high) + low);
}
return hexList;
}
/**
* 删除临时文件
*/
public void deleteTempFile(File file) {
if (file != null && file.exists()) {
if (file.delete()) {
log.debug("删除临时文件成功: {}", file.getAbsolutePath());
} else {
log.warn("无法删除临时文件: {}", file.getAbsolutePath());
}
}
}
/**
* 裁剪图像到目标尺寸
*/
private BufferedImage cropImage(BufferedImage img, int targetWidth, int targetHeight) {
int w = Math.min(img.getWidth(), targetWidth);
int h = Math.min(img.getHeight(), targetHeight);
return img.getSubimage(0, 0, w, h);
}
/**
* 将 BufferedImage 转换为 RGB565 格式的字节数组
*/
private byte[] convertToRGB565(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
byte[] result = new byte[width * height * 2];
int index = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
int r = ((rgb >> 16) & 0xFF) >> 3;
int g = ((rgb >> 8) & 0xFF) >> 2;
int b = (rgb & 0xFF) >> 3;
short pixel = (short) ((r << 11) | (g << 5) | b);
result[index++] = (byte) (pixel >> 8);
result[index++] = (byte) pixel;
}
}
return result;
}
/**
* 保存帧到本地(用于调试)
*/
public void saveFramesToLocal(List<BufferedImage> frames, String prefix) {
File outputDir = new File("output_frames");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
int index = 0;
for (BufferedImage frame : frames) {
try {
File outputImage = new File(outputDir, prefix + "_" + (index++) + ".png");
ImageIO.write(frame, "png", outputImage);
log.debug("保存帧图片成功: {}", outputImage.getAbsolutePath());
} catch (Exception e) {
log.error("保存帧图片失败", e);
}
}
}
}

View File

@ -49,9 +49,9 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://120.79.224.186:3366/fys-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
url: jdbc:mysql://47.120.79.150:3306/fys-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: 1fys@QWER..
password: Jq_123456#
# # 从库数据源
# slave:
# lazy: true
@ -98,13 +98,13 @@ spring:
spring.data:
redis:
# 地址
host: 120.79.224.186
host: 47.120.79.150
# 端口默认为6379
port: 26379
port: 6379
# 数据库索引
database: 2
# redis 密码必须配置
password: 1fys@QWER..
password: xhYc_djkl382^#780!
# 连接超时时间
timeout: 10s
# 是否开启ssl
@ -174,14 +174,14 @@ sms:
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: alibaba
# 有些称为accessKey有些称之为apiKey也有称为sdkKey或者appId。
access-key-id: LTAI5tDGfJd4kMvrGtvyzCHz
access-key-id: LTAI5tJdDNpZootsPQ5hdELx
# 称为accessSecret有些称之为apiSecret
access-key-secret: a4ZlVHVSYeMQHn0p1R18thA6xCdHQh
access-key-secret: mU4WtffcCXpHPz5tLwQpaGtLsJXONt
#模板ID 非必须配置如果使用sendMessage的快速发送需此配置
template-id: SMS_324526343
template-id: SMS_322180518
#模板变量 上述模板的变量
templateName: code
signature: 深圳市富源晟科技
signature: 湖北星汉研创科技
# sdk-app-id: 您的sdkAppId
config2:
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
@ -300,9 +300,9 @@ file:
# MQTT配置
mqtt:
username: admin
password: fys123456
url: tcp://47.107.152.87:1883
password: #YtvpSfCNG
url: tcp://www.cnxhyc.com:2883
subClientId: fys_subClient
subTopic: worker/location/#
subTopic: A/#
pubTopic: B/#
pubClientId: fys_pubClient

View File

@ -52,9 +52,9 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://47.107.152.87:3306/fys-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
url: jdbc:mysql://47.120.79.150:3306/fys-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: Jz_5623_cl1
password: Jq_123456#
# # 从库数据源
# slave:
# lazy: true
@ -101,13 +101,13 @@ spring:
spring.data:
redis:
# 地址
host: 47.107.152.87
host: 47.120.79.150
# 端口默认为6379
port: 6379
# 数据库索引
database: 1
# redis 密码必须配置
password: re_fs_11520631
password: xhYc_djkl382^#780!
# 连接超时时间
timeout: 10s
# 是否开启ssl
@ -177,14 +177,14 @@ sms:
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: alibaba
# 有些称为accessKey有些称之为apiKey也有称为sdkKey或者appId。
access-key-id: LTAI5tDGfJd4kMvrGtvyzCHz
access-key-id: LTAI5tJdDNpZootsPQ5hdELx
# 称为accessSecret有些称之为apiSecret
access-key-secret: a4ZlVHVSYeMQHn0p1R18thA6xCdHQh
access-key-secret: mU4WtffcCXpHPz5tLwQpaGtLsJXONt
#模板ID 非必须配置如果使用sendMessage的快速发送需此配置
template-id: SMS_324526343
template-id: SMS_322180518
#模板变量 上述模板的变量
templateName: code
signature: 深圳市富源晟科技
signature: 湖北星汉研创科技
config2:
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: tencent
@ -280,14 +280,19 @@ justauth:
# MQTT配置
mqtt:
username: admin
password: fys123456
url: tcp://47.107.152.87:1883
password: #YtvpSfCNG
url: tcp://47.120.79.150:3883
subClientId: fys_subClient
subTopic: A/#,worker/location/#
subTopic: A/#
pubTopic: B/#
pubClientId: fys_pubClient
enabled: false
# TTS语音交互配置
alibaba:
tts:
appKey: KTwSUKMrf2olFfjC
akId: LTAI5t6RsfCvQh57qojzbEoe
akSecret: MTqvK2mXYeCRkl1jVPndiNumyaad0R
# 文件存储路径
file:

View File

@ -1,7 +1,7 @@
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8001
port: 8000
servlet:
# 应用的访问路径
context-path: /

View File

@ -17,16 +17,21 @@ public interface GlobalConstants {
*/
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
/**
* 忘记密码验证码 redis key
*/
String FORGET_PASSWORD_CODE_KEY = GLOBAL_REDIS_KEY + "forget_password_codes:";
/**
* 注册验证码 redis key
*/
String REGISTER_CODE_KEY = GLOBAL_REDIS_KEY + "register_codes:";
/**
* 设备分享验证码 redis key
*/
String DEVICE_SHARE_CODES_KEY = GLOBAL_REDIS_KEY + "device_share_codes:";
/**
* 验证码 redis key
*/
String APP_FORGOT_PASSWORD_SMS_KEY = GLOBAL_REDIS_KEY + "app_sms_forgotPassword:";
/**
* 防重提交 redis key
*/

View File

@ -1,22 +0,0 @@
package com.fuyuanshen.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class AppLoginBody {
/**
* 手机号不能为空
*/
@NotBlank(message = "手机号不能为空")
private String userName;
/**
* 密码不能为空
*/
@NotBlank(message = "密码不能为空")
private String password;
private String tenantId;
}

View File

@ -0,0 +1,31 @@
package com.fuyuanshen.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
/**
* 密码登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AppPasswordLoginBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
// @Length(min = 5, max = 30, message = "{user.password.length.valid}")
private String password;
}

View File

@ -1,22 +0,0 @@
package com.fuyuanshen.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class AppSmsRegisterBody {
@NotBlank(message = "手机号不能为空")
private String phoneNumber;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "验证码不能为空")
private String verificationCode;
/**
* 租户ID
*/
private String tenantId;
}

View File

@ -18,14 +18,14 @@ public class PasswordLoginBody extends LoginBody {
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
// @Length(min = 2, max = 30, message = "{user.username.length.valid}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
// @Length(min = 5, max = 30, message = "{user.password.length.valid}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
private String password;
}

View File

@ -0,0 +1,108 @@
package com.fuyuanshen.common.core.utils.date;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 持续时间工具类
* 提供计算两个日期之间差值的方法,并以时分秒格式返回
*
* @author fys
*/
public class DurationUtils {
/**
* 计算两个日期之间的差值,返回时分秒格式的字符串
* 格式为 "X小时 Y分钟 Z秒"
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 时分秒格式的时间差字符串
*/
public static String getDurationBetween(Date startDate, Date endDate) {
if (startDate == null || endDate == null) {
return "0小时 0分钟 0秒";
}
long diffInMillis = Math.abs(endDate.getTime() - startDate.getTime());
long hours = TimeUnit.MILLISECONDS.toHours(diffInMillis);
long minutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
return String.format("%d小时 %d分钟 %d秒", hours, minutes, seconds);
}
/**
* 计算两个日期之间的差值,返回时分秒格式的字符串
* 仅显示非零单位
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 时分秒格式的时间差字符串,仅显示非零单位
*/
public static String getDurationBetweenPretty(Date startDate, Date endDate) {
if (startDate == null || endDate == null) {
return "0秒";
}
long diffInMillis = Math.abs(endDate.getTime() - startDate.getTime());
long hours = TimeUnit.MILLISECONDS.toHours(diffInMillis);
long minutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
StringBuilder result = new StringBuilder();
if (hours > 0) {
result.append(hours).append("小时 ");
}
if (minutes > 0) {
result.append(minutes).append("分钟 ");
}
if (seconds > 0 || result.length() == 0) {
result.append(seconds).append("");
}
return result.toString().trim();
}
/**
* 计算指定毫秒数的持续时间,返回时分秒格式的字符串
*
* @param millis 毫秒数
* @return 时分秒格式的持续时间字符串
*/
public static String getDurationFromMillis(long millis) {
long absMillis = Math.abs(millis);
long hours = TimeUnit.MILLISECONDS.toHours(absMillis);
long minutes = TimeUnit.MILLISECONDS.toMinutes(absMillis) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(absMillis) % 60;
return String.format("%d小时 %d分钟 %d秒", hours, minutes, seconds);
}
/**
* 计算指定毫秒数的持续时间,返回时分秒格式的字符串
* 仅显示非零单位
*
* @param millis 毫秒数
* @return 时分秒格式的持续时间字符串,仅显示非零单位
*/
public static String getDurationFromMillisPretty(long millis) {
long absMillis = Math.abs(millis);
long hours = TimeUnit.MILLISECONDS.toHours(absMillis);
long minutes = TimeUnit.MILLISECONDS.toMinutes(absMillis) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(absMillis) % 60;
StringBuilder result = new StringBuilder();
if (hours > 0) {
result.append(hours).append("小时 ");
}
if (minutes > 0) {
result.append(minutes).append("分钟 ");
}
if (seconds > 0 || result.length() == 0) {
result.append(seconds).append("");
}
return result.toString().trim();
}
}

View File

@ -24,13 +24,6 @@ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
private final byte[] body;
/**
* @param request
* @param privateKey
* @param headerFlag encrypt-key
* @throws IOException
*/
public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException {
super(request);
// 获取 AES 密码 采用 RSA 加密

View File

@ -19,36 +19,31 @@ public class EncryptUtilsTest {
String s = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=";
/**
* encrypt-key
*/
String s1 = EncryptUtils.encryptByRsa("MTIzNDU2Nzg5MGFiY2RlZg==", g);
System.out.println(s1);
System.out.println("-------------s1--------------");
String s2 = EncryptUtils.decryptByRsa("jJPaW7hgFXD/gjdkrfBOEUdXpPZnQg/LZUASoOJAOLU/XRVXO/5666CzyALjw7neK1ujvRuys4MdKCvr9cRARw==", s);
System.out.println(s2);
System.out.println("-------------s2--------------");
String s3 = EncryptUtils.decryptByBase64(s2);
System.out.println(s3);
System.out.println("-------------s2--------------");
String s4 = EncryptUtils.encryptByAes("123456", s3);
System.out.println(s4);
System.out.println("-------------s2--------------");
String s5 = EncryptUtils.decryptByAes(s4, s3);
System.out.println(s5);
System.out.println("-------------s2--------------");
// 1. 构造 LoginBody 对象
PasswordLoginBody loginBody = new PasswordLoginBody();
loginBody.setClientId("e5cd7e4891bf95d1d19206ce24a7b32e");
loginBody.setGrantType("password");
loginBody.setTenantId("894078");
loginBody.setCode("2");
loginBody.setUuid("d339659cea5245aab7df92642326218e");
loginBody.setCode("0");
loginBody.setUuid("1d6615668c7f410da77c4e002c601073");
// loginBody.setUsername("admin");
// loginBody.setPassword("admin123");
loginBody.setUsername("fel");
loginBody.setUsername("dyf");
loginBody.setPassword("123456");
// 2. 使用更清晰的方式拼接 JSON 字符串
@ -72,12 +67,9 @@ public class EncryptUtilsTest {
);
/**
* body
*/
String S5 = EncryptUtils.encryptByAes(jsonLoginBody, s3);
System.out.println(S5);
System.out.println("-------------s5--------------");
String S6 = EncryptUtils.decryptByAes(S5, s3);
System.out.println(S6);

View File

@ -394,6 +394,26 @@ public class RedisUtils {
return null;
}
}
/**
* 根据时间范围查询Sorted Set中的元素并降序排列
*
* @param key 键
* @param startTime 开始时间戳
* @param endTime 结束时间戳
* @return 指定时间范围内的元素集合(降序)
*/
public static Collection<String> zRangeByScoreDesc(String key, Long startTime, Long endTime) {
try {
RScoredSortedSet<String> sortedSet = CLIENT.getScoredSortedSet(key);
// 使用 valueRangeReversed 方法获取降序排列的结果
return sortedSet.valueRangeReversed(startTime, true, endTime, true);
} catch (Exception e) {
// 记录错误日志(如果项目中有日志工具的话)
// log.error("根据时间范围查询Sorted Set中的元素失败: key={}, startTime={}, endTime={}, error={}",
// key, startTime, endTime, e.getMessage(), e);
return null;
}
}
/**

View File

@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestSMSController {
@GetMapping("/test")
public void testSend() {
// 在创建完SmsBlend实例后再未手动调用注销的情况下框架会持有该实例可以直接通过指定configId来获取想要的配置如果你想使用

View File

@ -39,7 +39,7 @@ public class WebAppUserController extends BaseController {
/**
* 查询APP用户信息列表
* 查询APP用户信息列表
*/
// @SaCheckPermission("app:user:list")
@GetMapping("/list")
@ -67,7 +67,7 @@ public class WebAppUserController extends BaseController {
@SaCheckPermission("app:user:query")
@GetMapping("/{userId}")
public R<AppUserVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long userId) {
@PathVariable Long userId) {
return R.ok(appUserService.queryById(userId));
}
@ -105,5 +105,4 @@ public class WebAppUserController extends BaseController {
@PathVariable Long[] userIds) {
return toAjax(appUserService.deleteWithValidByIds(List.of(userIds), true));
}
}

View File

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

View File

@ -1,13 +1,14 @@
package com.fuyuanshen.app.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import com.fuyuanshen.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.util.Date;
/**
* 设备绑定关系对象 app_device_bind_record
@ -49,5 +50,9 @@ public class AppDeviceBindRecord extends TenantEntity {
*/
private Date bindingTime;
/**
* 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/
private Integer communicationMode;
}

View File

@ -1,14 +1,13 @@
package com.fuyuanshen.app.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fuyuanshen.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.util.Date;
/**
* APP用户信息对象 app_user
@ -96,9 +95,5 @@ public class AppUser extends TenantEntity {
*/
private String remark;
/**
* 地区
*/
private String region;
}

View File

@ -60,7 +60,7 @@ public class AppDeviceShareBo extends BaseEntity {
/**
* 分享时间
*/
private Date shareStartTime;
private Date shareEndTime;
private String shareStartTime;
private String shareEndTime;
}

View File

@ -45,5 +45,10 @@ public class AppOperationVideoBo extends BaseEntity {
*/
private String remark;
/**
* 多设备id
*/
private Long[] deviceIds;
}

View File

@ -1,19 +0,0 @@
package com.fuyuanshen.app.domain.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class APPForgotPasswordDTO {
@NotBlank(message = "手机号不能为空")
private String phoneNumber;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "验证码不能为空")
private String verificationCode;
}

View File

@ -1,12 +0,0 @@
package com.fuyuanshen.app.domain.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class APPForgotPasswordSmsDTO {
@NotBlank(message = "手机号不能为空")
private String phoneNumber;
}

View File

@ -1,6 +1,8 @@
package com.fuyuanshen.app.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

View File

@ -1,33 +0,0 @@
package com.fuyuanshen.app.domain.dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
/**
* @author: 默苍璃
* @date: 2025-06-1818:36
*/
@Data
public class APPUpdateUserDTO {
/**
* 用户昵称
*/
private String nickName;
/**
* 用户地区
*/
private String region;
/**
* 用户性别
*/
private String gender;
/**
* 用户头像
*/
private MultipartFile file;
}

View File

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

View File

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

View File

@ -1,39 +0,0 @@
package com.fuyuanshen.app.domain.vo;
import lombok.Data;
@Data
public class APPUserInfoVo {
/**
* 用户ID
*/
private Long id;
/**
* 用户昵称
*/
private String nickName;
/**
* 性别
*/
private String gender;
/**
* 手机号码
*/
private String phone;
/**
* 头像地址
*/
private String avatarPath;
/**
* 地区
*/
private String region;
}

View File

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

View File

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

View File

@ -109,4 +109,14 @@ public class AppDeviceShareVo implements Serializable {
* 告警状态(0解除告警1告警)
*/
private String alarmStatus;
/**
* 创建时间
*/
private String createTime;
/**
* 设备详情页面
*/
private String detailPageUrl;
}

View File

@ -19,12 +19,12 @@ public class AppFileVo {
* 文件名称
*/
private String fileName;
/**
* 文件url
*/
private String fileUrl;
/**
* 文件类型(1:操作说明2:产品参数)
*/
private Long fileType;
/**
* 文件url
*/
private String fileUrl;
}

View File

@ -14,5 +14,4 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AppUserMapper extends BaseMapperPlus<AppUser, AppUserVo> {
AppUser appFindByUsername(String phoneNumber);
}

View File

@ -1,5 +1,6 @@
package com.fuyuanshen.app.service;
import com.fuyuanshen.app.domain.AppBusinessFile;
import com.fuyuanshen.app.domain.vo.AppBusinessFileVo;
import com.fuyuanshen.app.domain.bo.AppBusinessFileBo;
import com.fuyuanshen.app.domain.vo.AppFileVo;
@ -50,6 +51,14 @@ public interface IAppBusinessFileService {
*/
Boolean insertByBo(AppBusinessFileBo bo);
/**
* 批量新增app业务文件
*
* @param bo 批量新增app业务文件
* @return 是否新增成功
*/
Boolean insertBatch(Collection<AppBusinessFile> bo,Boolean isBatch);
/**
* 修改app业务文件
*

View File

@ -1,5 +1,7 @@
package com.fuyuanshen.app.service;
import com.fuyuanshen.app.domain.AppBusinessFile;
import com.fuyuanshen.app.domain.AppOperationVideo;
import com.fuyuanshen.app.domain.vo.AppOperationVideoVo;
import com.fuyuanshen.app.domain.bo.AppOperationVideoBo;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
@ -49,6 +51,14 @@ public interface IAppOperationVideoService {
*/
Boolean insertByBo(AppOperationVideoBo bo);
/**
* 批量新增操作视频
*
* @param bo 批量新增操作视频
* @return 是否新增成功
*/
Boolean insertBatch(Collection<AppOperationVideo> bo);
/**
* 修改操作视频
*

View File

@ -1,13 +1,9 @@
package com.fuyuanshen.app.service;
import com.fuyuanshen.app.domain.bo.AppUserBo;
import com.fuyuanshen.app.domain.dto.APPForgotPasswordDTO;
import com.fuyuanshen.app.domain.dto.APPForgotPasswordSmsDTO;
import com.fuyuanshen.app.domain.dto.APPUpdateUserDTO;
import com.fuyuanshen.app.domain.vo.APPUserInfoVo;
import com.fuyuanshen.app.domain.vo.AppUserVo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.app.domain.bo.AppUserBo;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
@ -69,12 +65,4 @@ public interface IAppUserService {
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
APPUserInfoVo getUserInfo();
int updateUser(APPUpdateUserDTO bo);
int forgotPassword(APPForgotPasswordDTO bo);
int sendForgotPasswordSms(APPForgotPasswordSmsDTO dto);
}

Some files were not shown because too many files have changed in this diff Show More