Compare commits

..

86 Commits

Author SHA1 Message Date
lld 7f9189b7be 获取最近分享手机号 2026-04-06 04:18:39 +08:00
lld 80aa6d7922 管理员分享不需要连表 2026-04-06 03:55:23 +08:00
lld a0c45ea434 管理员不增加user_agri状态启用与否判断 2026-04-06 03:21:57 +08:00
lld f22bdf69b0 注册增加手机号 2026-04-06 03:13:11 +08:00
lld 2141261641 分享设备暂时提交 2026-04-05 19:22:46 +08:00
lld 965e6d542c 分享设备暂时提交 2026-04-05 18:48:15 +08:00
lld e07f0c1ebd 暂时提交 2026-04-05 18:16:17 +08:00
lld cb5c4615aa 暂时提交 2026-04-05 16:35:58 +08:00
lld da808bb692 暂时提交 2026-04-05 16:33:21 +08:00
lld 7b520eb8be 暂时提交 2026-04-04 00:58:24 +08:00
lld e5b0ff6fc0 message概览 2026-04-02 02:29:46 +08:00
lld b14c1d6c6d message概览 2026-04-02 01:31:26 +08:00
lld 2da9a54c30 修改日志推送逻辑 2026-04-01 21:07:48 +08:00
lld 468ea12025 大棚删除新增逻辑。完善系统消息中心逻辑 2026-04-01 20:59:33 +08:00
lld 1cc4f67d3b 温度上下限告警处理 2026-03-31 22:53:03 +08:00
lld 10ce0cf94a 温度上下限告警处理 2026-03-31 22:41:54 +08:00
lld 213fcd1fb8 温度上下限告警处理 2026-03-31 22:26:39 +08:00
lld 50c191a9a6 温度上下限告警处理 2026-03-31 22:17:37 +08:00
lld c2d651d38d 温度上下限告警处理 2026-03-31 21:25:00 +08:00
lld 780a4456e5 回执全部处理 2026-03-31 20:45:56 +08:00
lld 7cb223e15e 设备状态离线和设备离线接到数据中心 2026-03-31 20:03:24 +08:00
lld ad656d8cc3 获取所有设备最新的温湿度数据 2026-03-31 17:05:32 +08:00
lld c35551698c 获取所有设备最新的温湿度数据 2026-03-31 16:50:29 +08:00
lld af3be1f431 自动模式,设备日志 2026-03-31 15:37:39 +08:00
lld 3de90ccc70 自动模式,设备日志 2026-03-29 22:39:59 +08:00
lld 7498db935f 暂时提交 2026-03-29 22:13:08 +08:00
lld 6cd5788d15 修改打印规范 2026-03-29 18:02:15 +08:00
lld 28e02814bc 判断是否有当前imei的功能任务 2026-03-29 17:51:32 +08:00
lld 76469decc5 分布式锁逻辑执行后删除
自动模式不走是否启动定时任务逻辑、不走是否开启自动关逻辑
2026-03-29 17:33:22 +08:00
lld 575467d76a 历史温度,离线为null 2026-03-24 20:23:06 +08:00
lld ecb83531e8 定时清理,自动化修改、自动化误差上下温度改为0.5 2026-03-24 19:22:47 +08:00
lld 1d6d86b9d2 历史温度暂时提交 2026-03-19 16:09:42 +08:00
lld 34808ab31f 历史温度暂时提交 2026-03-19 14:00:41 +08:00
lld 7012d4c67c 卷被卷膜卷帘临时提交 2026-03-18 19:12:27 +08:00
lld 12fe073189 自动关逻辑初步验证完毕 2026-03-11 16:12:57 +08:00
lld fd0fd6e61e 参考温度展示 2026-03-09 23:51:22 +08:00
lld ccea1f5dd8 完善逻辑 2026-03-09 14:34:26 +08:00
lld 2828c209a1 完善逻辑 2026-03-08 20:17:47 +08:00
lld 97da9f0abb 在线状态 2026-03-08 18:29:25 +08:00
lld a2cb8ddee5 定时任务添加打印 2026-03-08 01:39:55 +08:00
lld a02514748c 每日0点执行删除sub 2026-03-08 00:36:38 +08:00
lld 1f00faccd9 修改离线推送主题 2026-03-08 00:01:40 +08:00
lld 7255e622cd 设备离线发送告警 2026-03-07 22:59:32 +08:00
lld b47415ab02 首页在线状态推送,设备离线告警 2026-03-07 22:45:38 +08:00
lld 8aea454417 完善自动模式 2026-03-06 23:13:44 +08:00
lld e542587200 自动化定时任务 2026-03-06 22:58:36 +08:00
lld a04d840f98 暂时提交 2026-03-06 22:36:10 +08:00
lld 814dcc0f58 暂时提交 2026-03-06 22:12:54 +08:00
lld 88b2ba4619 暂时提交 2026-03-06 22:08:26 +08:00
lld 6d62735dc4 暂时提交 2026-03-06 21:23:45 +08:00
lld 2b140bd180 自动化任务 2026-03-06 03:17:31 +08:00
lld 955adba5cb 自动化条件保存 2026-03-05 11:59:14 +08:00
lld cd36d21d01 自动化条件保存 2026-03-01 03:39:43 +08:00
lld 202e81dbaa 删除移动端 2026-02-21 00:09:43 +08:00
lld b60466e94a 修改移动端注册用户报错问题 2026-02-18 02:23:37 +08:00
lld 83667e5248 枚举代替硬编码 2026-02-16 01:52:55 +08:00
lld c4df5fd9a8 移动端可添加大棚。
大棚关联用户,
移动端可邀请用户
2026-02-16 01:00:17 +08:00
lld a2b6a770a4 新增卷帘 2026-02-08 20:50:03 +08:00
lld 020f351e66 解决ip报错 2026-02-08 17:06:25 +08:00
lld 2724342268 大棚关联用户 2026-02-08 16:47:20 +08:00
lld 14f9770c2c 大棚关联用户 2026-02-03 15:58:57 +08:00
lld 78311a1396 大棚关联用户 2026-02-03 10:37:07 +08:00
lld ca9c24ce90 设备控制 2026-02-01 19:47:08 +08:00
lld cbb0ea27f5 设备控制 2026-02-01 18:32:47 +08:00
lld 28c357ddf5 新增ack主题 2026-02-01 17:59:44 +08:00
lld 7dd7339953 指定配置文件,修改返回前端格式 2026-02-01 15:15:03 +08:00
lld 4aa7aeb8b4 延时任务获取最新状态, 2026-02-01 13:13:58 +08:00
lld f746cdac5c 大棚关联用户 2026-02-01 12:53:07 +08:00
lld b3c036bf80 设备控制完善日志 2026-02-01 02:03:11 +08:00
lld 2756492d89 设备控制完善日志 2026-02-01 01:48:01 +08:00
lld f6bf679a77 设备控制完善日志 2026-02-01 00:18:03 +08:00
lld 29ba2bd5bb 设备控制完善日志 2026-01-31 22:18:53 +08:00
lld d4be7bbd5d 设备控制完善日志,以及更新用户获取工具 2026-01-31 20:18:30 +08:00
lld c7d19dd442 设备控制完善日志,以及更新用户获取工具 2026-01-30 02:12:44 +08:00
lld f8f37ae22b 设备控制加日志 2026-01-29 23:16:42 +08:00
lld a3019355e0 控制指令修改,设备控制锁过期时间调整 2026-01-29 20:01:26 +08:00
lld 2d8edaccfc 更新控制指令锁过期时间 2026-01-29 17:39:32 +08:00
lld c9652702db 转发逻辑修改,先转发、再处理逻辑;企业微信报警信息修改 2026-01-29 16:44:09 +08:00
lld f47cf01cb0 拦截非json
mqtt状态查询不入操作日志
订阅设备查询优化
2026-01-28 00:38:34 +08:00
lld acb0028dd3 更新mqtt地址 2026-01-26 22:59:02 +08:00
xce 61a0ede957 大棚用户关联表 2026-01-26 16:43:05 +08:00
xce fb5067dda8 用户表id改为雪花 2026-01-25 21:34:01 +08:00
xce 2e7acda385 更新生产配置 2026-01-25 02:29:30 +08:00
xce da8928cf9c mqtt自动关 2026-01-25 00:17:08 +08:00
xce 251bfe63a6 增加企业微信通知· 2026-01-24 21:53:00 +08:00
xce 1c3dc095e4 mqtt架构 2026-01-24 19:53:48 +08:00
138 changed files with 13744 additions and 1634 deletions

View File

@ -12,10 +12,14 @@ COPY agri-admin.jar /app/agri-admin.jar
EXPOSE 8088 EXPOSE 8088
# -d 是 docker run 的后台参数,不属于程序启动参数 # -d 是 docker run 的后台参数,不属于程序启动参数
ENTRYPOINT ["java","-jar","/app/agri-admin.jar"] # 固定启动命令主体
ENTRYPOINT ["java", "-jar", "/app/agri-admin.jar"]
# 默认激活的配置文件,可被 docker run 覆盖
CMD ["--spring.profiles.active=druid,mqtt,prod"]
# 声明容器对外暴露端口(只是声明,真正映射端口靠 docker run -p # 声明容器对外暴露端口(只是声明,真正映射端口靠 docker run -p
# EXPOSE 8088 5005 # EXPOSE 8088 5005
# -d 是 docker run 的后台参数,不属于程序启动参数 # -d 是 docker run 的后台参数,不属于程序启动参数
# ENTRYPOINT ["java","-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005","-jar","/app/agri-admin.jar"] # ENTRYPOINT ["java","-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005","-jar","/app/agri-admin.jar", "--spring.profiles.active=druid,mqtt,prod"]

104
README.md
View File

@ -1,95 +1,15 @@
<p align="center"> # MQTT 压测脚本说明
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Agri v3.9.0</h1>
<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
<p align="center">
<a href="https://gitee.com/y_project/Agri-Vue/stargazers"><img src="https://gitee.com/y_project/Agri-Vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/Agri-Vue"><img src="https://img.shields.io/badge/Agri-v3.9.0-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/Agri-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
## 平台简介 ## 脚本列表
- pressure_up.sh设备状态上报压测
- pressure_mix.sh上报 + 控制混合压测
智能农业是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 ## 使用说明
1. 安装 mosquitto-clients
2. 修改脚本中的 BROKER / IMEI 范围
3. chmod +x *.sh
4. 运行脚本
* 前端采用Vue、Element UI。 ## 停止压测
* 后端采用Spring Boot、Spring Security、Redis & Jwt。 ```bash
* 权限认证使用Jwt支持多终端认证系统。 pkill mosquitto_pub
* 支持加载动态权限菜单,多方式轻松权限控制。
* 高效率开发,使用代码生成器可以一键生成前后端代码。
* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://gitcode.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。
* 提供了单应用版本[RuoYi-Vue-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast)Oracle版本[RuoYi-Vue-Oracle](https://gitcode.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。
* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
## 内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 通知公告:系统通知公告信息发布维护。
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
10. 登录日志:系统登录日志记录查询包含登录异常。
11. 在线用户:当前系统中活跃用户状态监控。
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
13. 代码生成前后端代码的生成java、html、xml、sql支持CRUD下载 。
14. 系统接口根据业务代码自动生成相关的api接口文档。
15. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
16. 缓存监控:对系统的缓存信息查询,命令统计等。
17. 在线构建器拖动表单元素生成相应的HTML代码。
18. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
## 在线体验
- admin/admin123
- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
演示地址http://vue.ruoyi.vip
文档地址http://doc.ruoyi.vip
## 演示图
<table>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-936ec82d1f4872e1bc980927654b6007307.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
</tr>
</table>
## 智能农业前后端分离交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/已满-151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/已满-228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [![加入QQ群](https://img.shields.io/badge/已满-191164766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) [![加入QQ群](https://img.shields.io/badge/174569686-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=PmYavuzsOthVqfdAPbo4uAeIbu7Ttjgc&authKey=p52l8%2FXa4PS1JcEmS3VccKSwOPJUZ1ZfQ69MEKzbrooNUljRtlKjvsXf04bxNp3G&noverify=0&group_code=174569686) 点击按钮入群。

View File

@ -3,16 +3,15 @@ package com.agri.web.controller.mqtt;
import com.agri.common.annotation.Log; import com.agri.common.annotation.Log;
import com.agri.common.core.domain.AjaxResult; import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType; import com.agri.common.enums.BusinessType;
import com.agri.framework.interceptor.MqttMessageHandler; import com.agri.framework.manager.AgriStatusManager;
import com.agri.framework.manager.MqttAutoOffManager;
import com.agri.framework.manager.MqttClientManager;
import com.agri.framework.manager.MqttSubscriptionManager;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
@ -27,7 +26,15 @@ public class MqttController {
private static final Logger log = LoggerFactory.getLogger(MqttController.class); private static final Logger log = LoggerFactory.getLogger(MqttController.class);
@Resource @Resource
private MqttMessageHandler mqttMessageHandler; private MqttSubscriptionManager mqttSubscriptionManager;
@Resource
private MqttClientManager mqttClientManager;
@Autowired
private MqttAutoOffManager mqttAutoOffManager;
@Autowired
private AgriStatusManager agriStatusManager;
/** /**
* *
@ -36,7 +43,7 @@ public class MqttController {
@Log(title = "订阅主题", businessType = BusinessType.INSERT) @Log(title = "订阅主题", businessType = BusinessType.INSERT)
public String subscribe(@RequestParam String clientId, @RequestParam String deviceId) { public String subscribe(@RequestParam String clientId, @RequestParam String deviceId) {
try { try {
mqttMessageHandler.subscribeDevice(clientId, deviceId); mqttSubscriptionManager.subscribeDevice(clientId, deviceId);
return "订阅成功"; return "订阅成功";
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.error("MQTT单个订阅失败{}", e.getMessage()); log.error("MQTT单个订阅失败{}", e.getMessage());
@ -53,7 +60,7 @@ public class MqttController {
@DeleteMapping("/single") @DeleteMapping("/single")
public String unsubscribe(@RequestParam String clientId, @RequestParam String deviceId) { public String unsubscribe(@RequestParam String clientId, @RequestParam String deviceId) {
try { try {
mqttMessageHandler.unsubscribeDevice(clientId, deviceId); mqttSubscriptionManager.unsubscribeDevice(clientId, deviceId);
return "取消订阅成功"; return "取消订阅成功";
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.error("MQTT单个取消订阅失败{}", e.getMessage()); log.error("MQTT单个取消订阅失败{}", e.getMessage());
@ -72,7 +79,7 @@ public class MqttController {
public AjaxResult subscribeAll(@RequestParam String clientId) { public AjaxResult subscribeAll(@RequestParam String clientId) {
try { try {
// 返回前端需要取消的MQTT主题列表 // 返回前端需要取消的MQTT主题列表
return AjaxResult.success(mqttMessageHandler.subscribeAllDeviceByUserId(clientId)); return AjaxResult.success(mqttSubscriptionManager.subscribeAllDeviceByUserId(clientId));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.error("MQTT批量订阅失败{}", e.getMessage()); log.error("MQTT批量订阅失败{}", e.getMessage());
// 异常时返回空列表,避免前端解析失败 // 异常时返回空列表,避免前端解析失败
@ -91,7 +98,7 @@ public class MqttController {
public List<String> unsubscribeAll(@RequestParam String clientId) { public List<String> unsubscribeAll(@RequestParam String clientId) {
try { try {
// 返回前端需要取消的MQTT主题列表 // 返回前端需要取消的MQTT主题列表
return mqttMessageHandler.unsubscribeAllDevice(clientId); return mqttSubscriptionManager.unsubscribeAllDevice(clientId);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.error("MQTT批量取消订阅失败{}", e.getMessage()); log.error("MQTT批量取消订阅失败{}", e.getMessage());
// 异常时返回空列表,避免前端解析失败 // 异常时返回空列表,避免前端解析失败
@ -109,7 +116,7 @@ public class MqttController {
@Log(title = "手动触发MQTT重连", businessType = BusinessType.OTHER) @Log(title = "手动触发MQTT重连", businessType = BusinessType.OTHER)
public String manualReconnect() { public String manualReconnect() {
try { try {
return mqttMessageHandler.manualReconnect(); return mqttClientManager.manualReconnect();
} catch (Exception e) { } catch (Exception e) {
log.error("MQTT手动重连异常", e); log.error("MQTT手动重连异常", e);
return "手动重连失败:" + e.getMessage(); return "手动重连失败:" + e.getMessage();
@ -121,13 +128,32 @@ public class MqttController {
* 便 * 便
*/ */
@GetMapping("/status") @GetMapping("/status")
@Log(title = "手动触发MQTT重连", businessType = BusinessType.SELECT) public AjaxResult getMqttStatus() {
public String getMqttStatus() {
try { try {
return mqttMessageHandler.getMqttStatus(); return AjaxResult.success(mqttClientManager.getMqttStatus());
} catch (Exception e) { } catch (Exception e) {
log.error("查询MQTT连接状态异常", e); log.error("查询MQTT连接状态异常", e);
return "查询状态失败:" + e.getMessage(); return AjaxResult.error("查询状态失败:" + e.getMessage());
} }
} }
/**
* 2
*/
@GetMapping("/device/{deviceId}")
public AjaxResult device(@PathVariable String deviceId) {
boolean hasTask = mqttAutoOffManager.hasAutoOffTask(deviceId);
return AjaxResult.success(hasTask);
}
@PostMapping("/getAgriStatus")
public void getAgriStatus(@RequestBody List<String> imeiList) {
if (imeiList.isEmpty()) {
log.info("大棚表无数据,结束推送");
return;
}
agriStatusManager.asyncBatchPushMqtt(agriStatusManager.batchCheckDeviceOnline(imeiList));
}
} }

View File

@ -1,8 +1,10 @@
package com.agri.web.controller.tool; package com.agri.web.controller.tool;
import com.agri.common.core.controller.BaseController; import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.core.domain.R; import com.agri.common.core.domain.R;
import com.agri.common.utils.StringUtils; import com.agri.common.utils.StringUtils;
import com.agri.framework.config.MqttConfig;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport; import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@ -11,6 +13,7 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -20,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -37,6 +41,9 @@ import java.util.Map;
@RequestMapping("/test/user") @RequestMapping("/test/user")
public class TestController extends BaseController public class TestController extends BaseController
{ {
@Resource
private MqttConfig.MqttMessageSender mqttMessageSender;
private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>(); private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();
{ {
users.put(1, new UserEntity(1, "admin", "admin123", "15888888888")); users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
@ -118,6 +125,16 @@ public class TestController extends BaseController
return R.fail("用户不存在"); return R.fail("用户不存在");
} }
} }
@RequestMapping("getStatus")
public void getStatus() throws MqttException {
mqttMessageSender.publish("dtu/864865085008135/down", "{\"jbk\":0,\"read\":true}");
logger.info("{\"jbk\":0,\"read\":true}");
}
} }
@ApiModel(value = "UserEntity", description = "用户实体") @ApiModel(value = "UserEntity", description = "用户实体")

View File

@ -0,0 +1,10 @@
# 日志配置
logging:
level:
com.agri: debug
org.springframework: warn
agri:
manager:
num: 5

View File

@ -6,7 +6,7 @@ spring:
druid: druid:
# 主库数据源 # 主库数据源
master: master:
url: jdbc:mysql://122.51.109.52:3306/agri?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai url: jdbc:mysql://49.233.181.63:3306/agri?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai
username: root username: root
password: lld123 password: lld123
# 从库数据源 # 从库数据源

View File

@ -1,15 +1,17 @@
spring: spring:
# MQTT配置 # MQTT配置
mqtt: mqtt:
host: tcp://122.51.109.52:1883 # 设备/后端的MQTT TCP地址 host: tcp://mq.xiaoces.com:1883 # 设备/后端的MQTT TCP地址
username: admin # Mosquitto共用账号 username: admin # Mosquitto共用账号
password: Admin#12345678 # Mosquitto密码 password: Admin#12345678 # Mosquitto密码
client-id: springboot-backend # 截取UUID前8位自动去横线 client-id: springboot-backend # 截取UUID前8位自动去横线
default-topic: dtu/+/up,frontend/+/control/+,frontend/+/online # 后端监听的主题 # 后端监听的主题
default-topic: dtu/+/up,dtu/+/ack,frontend/+/control/+,frontend/+/online, frontend/+/+/config
qos: 0 # 消息可靠性 qos: 0 # 消息可靠性
timeout: 60 # 连接超时 timeout: 60 # 连接超时
keep-alive: 60 # 心跳间隔 keep-alive: 60 # 心跳间隔
latest-ttl-seconds: 120 #设备最新状态缓存的过期时间(秒)。 latest-ttl-seconds: 120 #设备最新状态缓存的过期时间(秒)。
# 自动关闭任务线程池大小 # 自动关闭任务线程池大小
auto-off-thread-pool-size: 5 auto-off-thread-pool-size: 5
subc-ttl-seconds: 3600 # 在线新跳ttl subc-ttl-seconds: 3600 # 在线新跳ttl
dtu-ctl-lock-ttl: 20

View File

@ -0,0 +1,11 @@
# 日志配置
logging:
level:
com.agri: info
org.springframework: warn
# 大棚管理者
agri:
manager:
num: 5

View File

@ -6,12 +6,16 @@ agri:
version: 3.9.0 version: 3.9.0
# 版权年份 # 版权年份
copyrightYear: 2025 copyrightYear: 2025
# 文件路径 示例( Windows配置D:/agri/uploadPathLinux配置 /home/agri/uploadPath # 文件路径 示例( Windows配置D:/agri/uploadPathLinux配置 /opt/agri/uploadPath
profile: D:/agri/uploadPath profile: /opt/agri/uploadPath
# 获取ip地址开关 # 获取ip地址开关
addressEnabled: true addressEnabled: true
# 验证码类型 math 数字计算 char 字符验证 # 验证码类型 math 数字计算 char 字符验证
captchaType: math captchaType: math
# 卷膜滚轴长度和秒数
per-lap:
sec: 18 # 以秒为单位
len: 2.13 # 以cm为单位
# 开启增强 # 开启增强
knife4j: knife4j:
enable: true enable: true
@ -40,12 +44,6 @@ server:
# Tomcat启动初始化的线程数默认值10 # Tomcat启动初始化的线程数默认值10
min-spare: 100 min-spare: 100
# 日志配置
logging:
level:
com.agri: debug
org.springframework: warn
# 用户配置 # 用户配置
user: user:
password: password:
@ -61,7 +59,7 @@ spring:
# 国际化资源文件路径 # 国际化资源文件路径
basename: i18n/messages basename: i18n/messages
profiles: profiles:
active: druid,mqtt active: druid,mqtt,dev
# 文件上传 # 文件上传
servlet: servlet:
multipart: multipart:
@ -79,7 +77,7 @@ spring:
# redis 配置 # redis 配置
redis: redis:
# 地址 # 地址
host: 122.51.109.52 host: 49.233.181.63
# 端口默认为6379 # 端口默认为6379
port: 6379 port: 6379
# 数据库索引 # 数据库索引

View File

@ -16,7 +16,12 @@
</description> </description>
<dependencies> <dependencies>
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version> <!-- 该版本完全兼容JDK 8 -->
</dependency>
<!-- Spring框架基本的核心工具 --> <!-- Spring框架基本的核心工具 -->
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>

View File

@ -1,7 +1,9 @@
package com.agri.common.constant; package com.agri.common.constant;
import java.math.BigDecimal;
import java.util.Locale; import java.util.Locale;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Value;
/** /**
* *
@ -170,4 +172,11 @@ public class Constants
*/ */
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.agri.common.utils.file", "com.agri.common.config", "com.agri.generator" }; "org.springframework", "org.apache", "com.agri.common.utils.file", "com.agri.common.config", "com.agri.generator" };
public static final String JM1G = "jm1g";
public static final BigDecimal PER_LAP_LEN = BigDecimal.valueOf(2.13);
public static final BigDecimal PER_LAP_SEC = BigDecimal.valueOf(18);
} }

View File

@ -1,35 +1,53 @@
package com.agri.common.core.domain.entity; package com.agri.common.core.domain.entity;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.*;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.agri.common.annotation.Excel; import com.agri.common.annotation.Excel;
import com.agri.common.annotation.Excel.ColumnType; import com.agri.common.annotation.Excel.ColumnType;
import com.agri.common.annotation.Excel.Type; import com.agri.common.annotation.Excel.Type;
import com.agri.common.annotation.Excels; import com.agri.common.annotation.Excels;
import com.agri.common.annotation.Sensitive;
import com.agri.common.core.domain.BaseEntity; import com.agri.common.core.domain.BaseEntity;
import com.agri.common.enums.DesensitizedType;
import com.agri.common.xss.Xss; import com.agri.common.xss.Xss;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;
/** /**
* sys_user * sys_user
* *
* @author ruoyi * @author ruoyi
*/ */
@TableName("sys_user")
public class SysUser extends BaseEntity public class SysUser extends BaseEntity
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 用户ID */ /** 用户ID */
@Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号") @Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号")
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long userId; private Long userId;
/** 部门ID */ /** 部门ID */
@Excel(name = "部门编号", type = Type.IMPORT) @Excel(name = "部门编号", type = Type.IMPORT)
private Long deptId; private Long deptId;
/** 用户账号 */
@Excel(name = "clientId")
private String clientId;
/** 用户账号 */ /** 用户账号 */
@Excel(name = "登录名称") @Excel(name = "登录名称")
private String userName; private String userName;
@ -79,18 +97,23 @@ public class SysUser extends BaseEntity
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
}) })
@TableField(exist = false)
private SysDept dept; private SysDept dept;
/** 角色对象 */ /** 角色对象 */
@TableField(exist = false)
private List<SysRole> roles; private List<SysRole> roles;
/** 角色组 */ /** 角色组 */
@TableField(exist = false)
private Long[] roleIds; private Long[] roleIds;
/** 岗位组 */ /** 岗位组 */
@TableField(exist = false)
private Long[] postIds; private Long[] postIds;
/** 角色ID */ /** 角色ID */
@TableField(exist = false)
private Long roleId; private Long roleId;
public SysUser() public SysUser()
@ -98,6 +121,15 @@ public class SysUser extends BaseEntity
} }
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public SysUser(Long userId) public SysUser(Long userId)
{ {
this.userId = userId; this.userId = userId;
@ -317,6 +349,7 @@ public class SysUser extends BaseEntity
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("userId", getUserId()) .append("userId", getUserId())
.append("deptId", getDeptId()) .append("deptId", getDeptId())
.append("clientId", getClientId())
.append("userName", getUserName()) .append("userName", getUserName())
.append("nickName", getNickName()) .append("nickName", getNickName())
.append("email", getEmail()) .append("email", getEmail())

View File

@ -16,6 +16,7 @@ public class LoginBody
* *
*/ */
private String password; private String password;
private String phonenumber;
/** /**
* *
@ -66,4 +67,12 @@ public class LoginBody
{ {
this.uuid = uuid; this.uuid = uuid;
} }
public String getPhonenumber() {
return phonenumber;
}
public void setPhonenumber(String phonenumber) {
this.phonenumber = phonenumber;
}
} }

View File

@ -0,0 +1,33 @@
package com.agri.common.enums;
public enum AgriEnum {
SCAN(0, "扫码"),
INVITE(1, "邀请"),
OWNER(3, "大棚所有者"),
MANUAL(0, "手动模式"),
ALARM_CLOSE(0, "告警关闭"),
NO_DELETE(0, "未删除"),
ENABLED(1, "已生效"),
PENDING_ACCEPTANCE(2, "待接受邀请"),
WARN(0, "警告"),
SUCCESS(1, "成功"),
ERROR(2, "失败");
private final Integer code;
private final String desc;
AgriEnum(Integer code, String desc)
{
this.code = code;
this.desc = desc;
}
public Integer getCode() {
return code;
}
public String getDesc() {
return desc;
}
}

View File

@ -0,0 +1,34 @@
package com.agri.common.enums;
/**
*
*/
public enum DtuEnum {
JM1K("jm1k", "卷膜1开"),
JM1G("jm1g", "卷膜1关"),
JM2K("jm2k", "卷膜2开"),
JM2G("jm2g", "卷膜2关"),
JM3K("jm3k", "卷膜3开"),
JM3G("jm3g", "卷膜3关"),
JBK("jbk", "卷被开"),
JBG("jbg", "卷被关");
private final String code;
private final String name;
DtuEnum(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,32 @@
package com.agri.common.enums;
/**
* @ClassName TempCommandStatus
* @Description TODO
* @Author lld
* @Date 2026/3/6 2:46
* @Version 1.0
*/
public enum TempCommandStatus {
OPEN(1, "下发开指令"),
CLOSE(2, "下发关指令"),
NO_OPERATE(0, "不操作");
private final int code;
private final String desc;
TempCommandStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
// 使用status.getCode() → 1/2/0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
package com.agri.common.utils;
/**
* @ClassName dd
* @Description TODO
* @Author lld
* @Date 2026/3/6 1:44
* @Version 1.0
*/
import com.agri.common.constant.Constants;
import org.springframework.beans.factory.annotation.Value;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* +
*/
public class RollerTimeCalculator {
// 私有化构造器,禁止实例化
private RollerTimeCalculator() {}
/**
*
* @param targetLen cm
* @return
*/
public static int calculateRunTime(BigDecimal targetLen) {
// 边界处理长度≤0时返回0
if (targetLen == null || targetLen.compareTo(BigDecimal.ZERO)<=0) {
return 0;
}
// 核心公式:时间 = (长度 / 每圈长度) × 每圈时间
BigDecimal cycleCount = targetLen.divide(Constants.PER_LAP_LEN,2, RoundingMode.HALF_UP);
return cycleCount.multiply(Constants.PER_LAP_SEC).setScale(2, RoundingMode.HALF_UP).intValue(); // 四舍五入取整
}
/**
*
* /perLapLenperLapSec
*/
/* public static long calculateRunTimeWithConfig(double targetLen, String imei) {
// 从配置表/缓存读取该设备的单圈长度、单圈时间(适配不同设备参数差异)
Double deviceCmPerCycle = ConfigCache.getVal(imei + "_perLapLen", perLapLen);
Long deviceSecPerCycle = ConfigCache.getVal(imei + "_sec_per_cycle", perLapSec);
return Math.round(targetLen / deviceCmPerCycle * deviceSecPerCycle);
}*/
}

View File

@ -1,17 +1,20 @@
package com.agri.common.utils; package com.agri.common.utils;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.PatternMatchUtils;
import com.agri.common.constant.Constants; import com.agri.common.constant.Constants;
import com.agri.common.constant.HttpStatus; import com.agri.common.constant.HttpStatus;
import com.agri.common.core.domain.entity.SysRole; import com.agri.common.core.domain.entity.SysRole;
import com.agri.common.core.domain.model.LoginUser; import com.agri.common.core.domain.model.LoginUser;
import com.agri.common.exception.ServiceException; import com.agri.common.exception.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.PatternMatchUtils;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/** /**
* *
@ -21,6 +24,8 @@ import com.agri.common.exception.ServiceException;
public class SecurityUtils public class SecurityUtils
{ {
private static final Logger log = LoggerFactory.getLogger(SecurityUtils.class);
/** /**
* ID * ID
**/ **/
@ -28,7 +33,11 @@ public class SecurityUtils
{ {
try try
{ {
return getLoginUser().getUserId(); LoginUser loginUser = getLoginUser();
if (loginUser!= null) {
return loginUser.getUserId();
}
return null;
} }
catch (Exception e) catch (Exception e)
{ {
@ -58,7 +67,11 @@ public class SecurityUtils
{ {
try try
{ {
return getLoginUser().getUsername(); LoginUser loginUser = getLoginUser();
if (loginUser !=null ) {
return loginUser.getUsername();
}
return null;
} }
catch (Exception e) catch (Exception e)
{ {
@ -71,14 +84,18 @@ public class SecurityUtils
**/ **/
public static LoginUser getLoginUser() public static LoginUser getLoginUser()
{ {
try try {
{ Authentication authentication = getAuthentication();
return (LoginUser) getAuthentication().getPrincipal(); if (authentication == null || !(authentication.getPrincipal() instanceof LoginUser)) {
} return null; // 无用户上下文时返回 null
catch (Exception e) }
{ return (LoginUser) authentication.getPrincipal();
throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
} catch (Exception e) {
log.error("获取用户信息异常: {}", HttpStatus.UNAUTHORIZED);
} }
return null;
} }
/** /**
@ -114,6 +131,15 @@ public class SecurityUtils
return passwordEncoder.matches(rawPassword, encodedPassword); return passwordEncoder.matches(rawPassword, encodedPassword);
} }
/**
*
*
* @return
*/
public static boolean isAdmin()
{
return isAdmin(getUserId());
}
/** /**
* *
* *

View File

@ -0,0 +1,42 @@
package com.agri.common.utils;
import com.agri.common.enums.TempCommandStatus;
import java.math.BigDecimal;
/**
*
*/
public class TempJudgeUtil {
// 条件参考温度允许的上下误差(抽成常量,后续可配置化)
public static final BigDecimal TEMP_ERROR_RANGE = BigDecimal.valueOf(0.5);
/**
*
*
* @param currentTemp DTU
* @param refTemp sys_auto_termtemp
* @return OPEN()/CLOSE()/NO_OPERATE()
*/
public static TempCommandStatus judgeTempCommand(BigDecimal currentTemp, BigDecimal refTemp) {
// 1. 空值校验企业级必备避免NPE
if (currentTemp == null || refTemp == null) {
return TempCommandStatus.NO_OPERATE;
}
// 2. 计算当前温度与参考温度的差值(当前温度 - 参考温度)
BigDecimal tempDiff = currentTemp.subtract(refTemp);
// 3. 按规则判断状态
if (tempDiff.compareTo(TEMP_ERROR_RANGE) > 0) {
// 当前温度 > 参考温度 + 误差 → 下发开指令
return TempCommandStatus.OPEN;
} else if (tempDiff.compareTo(TEMP_ERROR_RANGE.negate()) < 0) {
// 当前温度 < 参考温度 - 误差 → 下发关指令
return TempCommandStatus.CLOSE;
} else {
// 差值在[-误差, +误差]范围内 → 不操作
return TempCommandStatus.NO_OPERATE;
}
}
}

View File

@ -0,0 +1,41 @@
package com.agri.common.utils;
/**
* @ClassName TimeConvertUtil
* @Description TODO
* @Author lld
* @Date 2026/3/6 2:16
* @Version 1.0
*/
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class TimeConvertUtil {
// 定义常用时间格式(企业级:固化为常量,避免重复创建)
public static final DateTimeFormatter DEFAULT_DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* LocalDateTimeyyyy-MM-dd HH:mm:ss
*/
public static LocalDateTime strToLocalDateTime(String timeStr) {
// 空值/空白校验(企业级必备)
if (timeStr == null || timeStr.trim().isEmpty()) {
return null;
}
// 标准格式直接解析
return LocalDateTime.parse(timeStr.trim(), DEFAULT_DATETIME_FORMAT);
}
/**
*
*/
public static LocalDateTime strToLocalDateTimeSafe(String timeStr) {
try {
return strToLocalDateTime(timeStr);
} catch (Exception e) {
// 日志记录转换失败原因(企业级:必加)
System.err.println("时间字符串转换失败str=" + timeStr + ",错误:" + e.getMessage());
return null;
}
}
}

View File

@ -0,0 +1,46 @@
package com.agri.common.utils;
/**
* @ClassName TimeRangeUtil
* @Description TODO
* @Author lld
* @Date 2026/3/6 2:09
* @Version 1.0
*/
import java.time.LocalDateTime;
import java.time.LocalTime;
public class TimeRangeUtil {
/**
* dtuauto_term
*/
public static boolean isTimeInRange(LocalDateTime dtuTime, LocalDateTime startTime, LocalDateTime endTime) {
if (dtuTime == null || startTime == null || endTime == null) {
return false;
}
LocalTime currentTime = dtuTime.toLocalTime();
LocalTime beforeTime = startTime.toLocalTime();
LocalTime afterTime = endTime.toLocalTime();
if (startTime.isBefore(endTime)) {
// 正常时段08:00:00 - 18:00:00
return !currentTime.isBefore(beforeTime) && !currentTime.isAfter(afterTime);
} else {
// 跨天时段22:00:00 - 06:00:00
return currentTime.isAfter(beforeTime) || currentTime.isBefore(afterTime);
}
}
/**
* ts
*/
public static boolean isTsInRange(long ts, LocalDateTime startTime, LocalDateTime endTime) {
LocalDateTime dtuTime = LocalDateTime.ofInstant(
java.time.Instant.ofEpochMilli(ts),
java.time.ZoneId.systemDefault()
);
return isTimeInRange(dtuTime, startTime, endTime);
}
}

View File

@ -1,5 +1,22 @@
package com.agri.common.utils.http; package com.agri.common.utils.http;
import com.agri.common.constant.Constants;
import com.agri.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -11,17 +28,6 @@ import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.agri.common.constant.Constants;
import com.agri.common.utils.StringUtils;
import org.springframework.http.MediaType;
/** /**
* http * http
@ -32,6 +38,69 @@ public class HttpUtils
{ {
private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
// 静态RestTemplate全局唯一线程安全
private static final RestTemplate restTemplate;
// 静态代码块初始化RestTemplate项目启动时执行一次
static {
// 配置超时时间,避免连接卡死
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000); // 连接超时5秒
factory.setReadTimeout(5000); // 读取超时5秒
// 初始化静态RestTemplate
restTemplate = new RestTemplate(factory);
}
/**
* POST
* @param url
* @param requestBody /Map/JSON
* @param contentType Content-Typeapplication/json
* @param responseType String.class/.class
* @return
*/
public static <T, R> R post(String url, T requestBody, String contentType, Class<R> responseType) {
// 前置校验:避免空指针
if (url == null || url.isEmpty()) {
throw new IllegalArgumentException("请求URL不能为空");
}
if (contentType == null || contentType.isEmpty()) {
throw new IllegalArgumentException("Content-Type不能为空");
}
if (restTemplate == null) {
throw new IllegalStateException("RestTemplate初始化失败请检查配置");
}
try {
// 1. 构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(contentType));
// 2. 封装请求实体
HttpEntity<T> requestEntity = new HttpEntity<>(requestBody, headers);
// 3. 发送POST请求
return restTemplate.postForObject(url, requestEntity, responseType);
} catch (RestClientException e) {
// 详细异常信息,方便定位问题
throw new RuntimeException(
String.format("HTTP POST请求失败url=%s, contentType=%s", url, contentType),
e
);
}
}
/**
* POSTString
* @param url
* @param requestBody
* @param contentType Content-Type
* @return
*/
public static <T> String post(String url, T requestBody, String contentType) {
return post(url, requestBody, contentType, String.class);
}
/** /**
* URL GET * URL GET
* *

View File

@ -19,7 +19,7 @@ public class AddressUtils
private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
// IP地址查询 // IP地址查询
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; public static final String IP_URL = "https://whois.pconline.com.cn/ipJson.jsp";
// 未知地址 // 未知地址
public static final String UNKNOWN = "XX XX"; public static final String UNKNOWN = "XX XX";

View File

@ -0,0 +1,36 @@
package com.agri.common.utils.wechat;
import cn.hutool.core.map.MapUtil;
import com.agri.common.utils.http.HttpUtils;
import org.springframework.http.MediaType;
import java.util.Map;
/**
* @Auther: jone
* @Date: 2026/1/24 - 01 - 24 - 21:12
* @Description: com.agri.common.utils.wechat
* @version: 1.0
*/
public class WxUtil {
private static final String WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=3cfeb466-76b9-4cf9-b03b-2324a2c3900f";
/**
*
* @param param
* @return
*/
public static Map pushText(String param) {
// 1. 构建请求参数
Map<String, Object> requestBody = MapUtil.<String, Object>builder()
.put("msgtype", "text")
.put("text", MapUtil.<String, Object>builder()
.put("content", param)
.build())
.build();
return HttpUtils.post(WEBHOOK_URL,
requestBody, MediaType.APPLICATION_JSON_VALUE, Map.class);
}
}

View File

@ -31,7 +31,7 @@ public class MybatisPlusHandler implements MetaObjectHandler {
// 填充创建时间字段名createTime当前时间 // 填充创建时间字段名createTime当前时间
this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
// 示例填充创建人假设从上下文获取当前用户ID // 示例填充创建人假设从上下文获取当前用户ID
this.strictInsertFill(metaObject, "createBy", String.class, getLoginUser().getUsername()); this.strictInsertFill(metaObject, "createBy", String.class, getLoginUser()!=null?getLoginUser().getUsername():"");
} }
// 更新操作时自动填充 // 更新操作时自动填充
@ -40,6 +40,6 @@ public class MybatisPlusHandler implements MetaObjectHandler {
// 填充更新时间字段名updateTime当前时间 // 填充更新时间字段名updateTime当前时间
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
// 示例:填充更新人 // 示例:填充更新人
this.strictUpdateFill(metaObject, "updateBy", String.class, getLoginUser().getUsername()); this.strictUpdateFill(metaObject, "updateBy", String.class, getLoginUser()!=null?getLoginUser().getUsername():"");
} }
} }

View File

@ -0,0 +1,12 @@
package com.agri.framework.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration // 必须加
public class RestTemplateConfig {
@Bean // 必须加声明RestTemplate为Spring Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@ -111,7 +111,7 @@ public class SecurityConfig
.authorizeHttpRequests((requests) -> { .authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage","/api/mqtt/status").permitAll() requests.antMatchers("/login", "/register", "/captchaImage","/api/mqtt/status","/test/user/getStatus").permitAll()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()

View File

@ -5,6 +5,8 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
@ -60,4 +62,17 @@ public class ThreadPoolConfig
} }
}; };
} }
@Bean("mqttPushExecutor")
public Executor mqttPushExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(2000);
executor.setThreadNamePrefix("mqtt-push-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
} }

View File

@ -0,0 +1,148 @@
package com.agri.framework.interceptor;
import com.agri.framework.manager.MqttAutoOffManager;
import com.agri.system.domain.SysAgriLimit;
import com.agri.system.domain.SysDevOperLog;
import com.agri.system.service.ISysAgriLimitService;
import com.agri.system.service.ISysDevOperLogService;
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@Component
public class DeviceAckHandler {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(DeviceAckHandler.class);
/**
* Redis线
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* /
*/
@Resource
private MqttAutoOffManager mqttAutoOffManager;
/**
*
*/
@Resource
private ISysAgriLimitService agriLimitService;
@Autowired
private ISysDevOperLogService sysDevOperLogService;
// 初始化映射(建议放在类初始化块/构造方法中,只初始化一次)
private static final Map<String, Function<SysAgriLimit, Integer>> LIMIT_MAP = new HashMap<>();
private static final Set<String> VALID_FUNC_CODES = new HashSet<>();
static {
LIMIT_MAP.put("jm1g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm1gLimit())));
LIMIT_MAP.put("jm2g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm2gLimit())));
LIMIT_MAP.put("jbg1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJbgLimit())));
LIMIT_MAP.put("jm3g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm3gLimit())));
LIMIT_MAP.put("jm2k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm2kLimit())));
LIMIT_MAP.put("jm3k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm3kLimit())));
LIMIT_MAP.put("jbk1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJbkLimit())));
LIMIT_MAP.put("jm1k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm1kLimit())));
VALID_FUNC_CODES.add("jm1g");
VALID_FUNC_CODES.add("jm2g");
VALID_FUNC_CODES.add("jbg");
VALID_FUNC_CODES.add("jm3g");
VALID_FUNC_CODES.add("jm2k");
VALID_FUNC_CODES.add("jm3k");
VALID_FUNC_CODES.add("jbk");
VALID_FUNC_CODES.add("jm1k");
}
public void isStartAutoOffTask(JSONObject payloadObj, String deviceId, String payload) {
if (payloadObj.containsKey("suc") && payloadObj.containsKey("prop")) {
JSONObject propObj = payloadObj.getJSONObject("prop");
if (propObj != null && !propObj.isEmpty()) {
boolean suc = payloadObj.getBooleanValue("suc");
for (Map.Entry<String, Object> propEntry : propObj.entrySet()) {
String funcType = propEntry.getKey();
Integer funcValue = parseFuncValue(propEntry.getValue());
processAck(deviceId, funcType, funcValue, suc);
}
if (propObj.size() > 1) {
log.info("【设备回执】设备{}的{}个功能已处理", deviceId, propObj.size());
}
}
}
}
private void processAck(String deviceId, String funcType, Integer funcValue, boolean suc) {
String lockKey = "lock:" + deviceId + ":" + funcType;
Boolean delete = stringRedisTemplate.delete(lockKey);
log.info("【设备回执】设备{}的{}功能执行完成,已释放锁:{},{}", deviceId, funcType, lockKey, delete);
int runTime = 0;
if (suc && StringUtils.hasText(funcType) && funcValue != null && funcValue == 1) {
SysAgriLimit agriLimit = agriLimitService.lambdaQuery()
.eq(SysAgriLimit::getImei, deviceId)
.one();
if (agriLimit != null) {
int autoOffSeconds = LIMIT_MAP.getOrDefault(funcType, k -> 0).apply(agriLimit);
runTime = autoOffSeconds;
if (autoOffSeconds > 0) {
mqttAutoOffManager.scheduleAutoOff(deviceId, funcType, autoOffSeconds);
log.debug("【自动关任务】标记需要执行deviceId={}, funcType={}, delay={}s", deviceId, funcType, autoOffSeconds);
}
}
}
if (suc && StringUtils.hasText(funcType) && funcValue != null && funcValue == 0) {
mqttAutoOffManager.cancelAutoOff(deviceId, funcType);
}
boolean isTask = (Objects.equals(funcValue, 1)) && (runTime > 0);
sysDevOperLogService.lambdaUpdate()
.eq(SysDevOperLog::getImei, deviceId)
.eq(SysDevOperLog::getFuncCode, funcType)
.eq(SysDevOperLog::getOpType, funcValue)
.eq(SysDevOperLog::getLockAcquired, 1)
.orderByDesc(SysDevOperLog::getCreateTime)
.last("LIMIT 1")
.set(SysDevOperLog::getAckReceived, 1)
.set(SysDevOperLog::getAckSuc, suc ? 1 : 0)
.set(SysDevOperLog::getIsLockSuc, delete ? 1 : 0)
.set(SysDevOperLog::getIsTask, isTask ? 1 : 0)
.set(isTask, SysDevOperLog::getRunTime, runTime)
.set(isTask, SysDevOperLog::getNoTaskReason, runTime > 0 ? null : "【自动关任务】标记不符合执行运行时间未配置,当前运行时间:【" + runTime + " s】")
.set(SysDevOperLog::getUpdateBy, "设备回执")
.set(SysDevOperLog::getAck, "{funcType:" + funcType + ", value:" + funcValue + "}")
.update();
}
private Integer parseFuncValue(Object value) {
if (value == null) {
return null;
}
try {
return Integer.parseInt(String.valueOf(value));
} catch (NumberFormatException e) {
return null;
}
}
}

View File

@ -0,0 +1,323 @@
package com.agri.framework.interceptor;
import com.agri.common.utils.wechat.WxUtil;
import com.agri.framework.config.MqttConfig;
import com.agri.framework.manager.MqttAutoOffManager;
import com.agri.framework.manager.MqttSubscriptionManager;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysMessage;
import com.agri.system.domain.SysUserAgri;
import com.agri.system.service.AgriService;
import com.agri.system.service.ISysAgriInfoService;
import com.agri.system.util.UrlEncodeUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.collections4.CollectionUtils;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
*
*
* 1.
* 2.
* 3.
* 4.
* JDK 8
*/
@Component
public class DeviceStatusHandler {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(DeviceStatusHandler.class);
/**
* Redis线
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* MQTTMqttConfig
*/
@Resource
private MqttConfig.MqttMessageSender mqttMessageSender;
/**
* MQTTRedis
*/
@Resource
private MqttSubscriptionManager mqttSubscriptionManager;
@Autowired
private DeviceAckHandler deviceAckHandler;
@Autowired
private ISysAgriInfoService agriInfoService;
// 新增最新状态缓存TTL设备每10秒上报一次缓存一小段时间即可
@Value("${spring.mqtt.latest-ttl-seconds:120}")
private int latestTtlSeconds;
@Autowired
private AgriService agriService;
private static final String AUTO_MODE = "auto_mode";
private static final String AUTO_OFF = "autooff";
@Resource
private FrontendControlHandler frontendControlHandler;
/**
* /
*/
@Resource
private MqttAutoOffManager mqttAutoOffManager;
private static final Set<String> VALID_FUNC_CODES = new HashSet<>();
static {
VALID_FUNC_CODES.add("jm1g");
VALID_FUNC_CODES.add("jm2g");
VALID_FUNC_CODES.add("jbg");
VALID_FUNC_CODES.add("jm3g");
VALID_FUNC_CODES.add("jm2k");
VALID_FUNC_CODES.add("jm3k");
VALID_FUNC_CODES.add("jbk");
VALID_FUNC_CODES.add("jm1k");
}
/**
*
*/
public void handle(String topic, String payload) {
// log.info("【设备处理】JSON解析{}",payloadObj);
// 解析设备ID主题格式为dtu/{deviceId}/up分割后第2个元素是设备ID
String deviceId = extractDeviceId(topic);
if (deviceId == null) return;
String[] segments = topic.split("/");
String action = segments[2];
if ("down".equals(action) || !isJsonObjectLike(payload)) return;
// 第一步解析JSON非有效JSON直接return
JSONObject payloadObj;
try {
payloadObj = JSON.parseObject(payload);
} catch (Exception e) {
log.error("【设备处理】JSON解析失败payload={}", payload, e);
return;
}
if (payloadObj == null || payloadObj.isEmpty()) {
log.warn("【设备处理】JSON解析后为空payload={}", payload);
return;
}
boolean isAck = payloadObj.containsKey("suc") && payloadObj.containsKey("prop");
JSONObject sendObj = payloadObj; // 默认直接用原对象
// 如果是回执,先拿 funcType
if (isAck) {
JSONObject propObj = payloadObj.getJSONObject("prop");
if (propObj != null && !propObj.isEmpty()) {
String funcType = propObj.entrySet().iterator().next().getKey();
String lockKey = "lock:" + deviceId + ":" + funcType;
// 读取锁的 value比如 autooff / user:1001
String lockHolder = stringRedisTemplate.opsForValue().get(lockKey);
if (lockHolder != null) {
sendObj = new JSONObject(payloadObj); // 只在需要时复制
sendObj.put("clientId", lockHolder);
// 如果相等则为自动模式直接退出方法 不转发ack消息 自动关也不应该转发
if (AUTO_MODE.equals(lockHolder) || AUTO_OFF.equals(lockHolder)) {
return;
}
}
}
}
// 转发消息
forwardPayload(deviceId, payload,payloadObj,action, sendObj, isAck);
// 获取第二个动态段,如"up"或"ack"
if ("ack".equals(action)) {
deviceAckHandler.isStartAutoOffTask(payloadObj,deviceId,payload);
return;
}
// payload全部为数字
if ("up".equals(action) && isAllKeysDigit(payloadObj)) {
SysAgriInfo agriInfo = agriInfoService.lambdaQuery()
.eq(SysAgriInfo::getImei, deviceId)
.one();
if (agriInfo == null) {
return;
}
// 温度上下限
BigDecimal tempUp = agriInfo.getTempUp();
BigDecimal tempLow = agriInfo.getTempLow();
// 湿度上下限
BigDecimal humiUp = agriInfo.getHumiUp();
BigDecimal humiLow = agriInfo.getHumiLow();
if (BigDecimal.ZERO.compareTo(tempUp) == 0
&& BigDecimal.ZERO.compareTo(tempLow) == 0
&& BigDecimal.ZERO.compareTo(humiUp) == 0
&& BigDecimal.ZERO.compareTo(humiLow) == 0) return;
List<SysAgriInfo> msgList = new ArrayList<>();
// 仅当up且所有key为数字时才更新最新状态缓存
for (String key : payloadObj.keySet()) {
BigDecimal value = payloadObj.getBigDecimal(key)
.divide(new BigDecimal(10),1, RoundingMode.HALF_UP);
String valueIndex = key.substring(2);
SysAgriInfo sysAgriInfo = new SysAgriInfo();
BeanUtils.copyProperties(agriInfo, sysAgriInfo);
// 温度
if (key.startsWith("20")) {
// 温度大于温度上限
if (value.compareTo(tempUp) > 0 ) {
agriInfo.setTitle("温度异常");
agriInfo.setMsg("温度"+valueIndex+ "异常!高于上限"+tempUp+"℃!");
msgList.add(agriInfo);
} else if (value.compareTo(tempLow) < 0) {
agriInfo.setTitle("温度异常");
agriInfo.setMsg("温度"+valueIndex+ "异常!低于下限"+tempLow+"℃!");
msgList.add(agriInfo);
}
continue;
}
// 湿度
if (key.startsWith("10")) {
// 湿度大于湿度上限
if (value.compareTo(humiUp) > 0 ) {
agriInfo.setTitle("湿度异常");
agriInfo.setMsg("湿度"+valueIndex+ "异常!高于上限"+humiUp+"RH%");
msgList.add(agriInfo);
} else if (value.compareTo(humiLow) < 0) {
agriInfo.setTitle("湿度异常");
agriInfo.setMsg("湿度"+valueIndex+ "异常!低于下限"+humiLow+"RH%");
msgList.add(agriInfo);
}
}
}
if (!msgList.isEmpty()) {
List<SysMessage> messages = agriService.saveMessage(msgList);
try {
frontendControlHandler.sendAlarmMessage(messages);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
private void forwardPayload(String deviceId,String payload,
JSONObject payloadObj,String action, JSONObject sendObj, boolean isAck) {
try {
// 非回执消息:正常转发给订阅前端
// 查询Redis中订阅该设备的前端列表sub:{deviceId}
Set<String> subscribedClients = stringRedisTemplate.opsForSet().members("sub:" + deviceId);
if (subscribedClients != null && !subscribedClients.isEmpty()) {
// 推送给每个订阅的前端
// 方案B不再依赖online:改为校验subc:{clientId}是否仍包含deviceId取消订阅失败/异常退出兜底)
List<String> clients = new ArrayList<>(subscribedClients);
// 判断subc是否还存在 一次性查全部 获取失效的clientId
List<Boolean> stillSubs = mqttSubscriptionManager.pipeIsMemberSubc(clients, deviceId);
// 关系不存在清理sub:{deviceId}残留,避免一直给前端发
List<String> stale = null;
for (int i = 0; i < clients.size(); i++) {
String clientId = clients.get(i);
boolean stillSub = i < stillSubs.size() && Boolean.TRUE.equals(stillSubs.get(i));
if (!stillSub) {
if (stale == null) {
stale = new ArrayList<>();
}
// false不存在添加队列
stale.add(clientId);
continue;
}
// 前端专属主题frontend/{clientId}/dtu/{deviceId}/listener
String frontendTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
if ("ack".equals(action)) {
frontendTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/ack";
}
// 发布消息
mqttMessageSender.publish(frontendTopic, sendObj.toJSONString());
// log.info("【设备状态转发】设备{} → 前端{},主题:{}", deviceId, clientId, frontendTopic);
}
// 删掉设备对应的客户端
if (stale != null && !stale.isEmpty()) {
mqttSubscriptionManager.pipeSRemSub(deviceId, stale);
}
} else {
// 优化替换System.out为log.info
// log.info("【设备状态转发】设备{}无订阅前端,跳过转发", deviceId);
}
// 第三步仅处理非回执的设备状态包且仅当是8个功能码结构就写入Redis
// 有没有人订阅都得写,只要发送设备开的指令成功了就得写
if (!isAck) {
// 1) 先校验状态包是否包含8个固定功能码核心只有这种结构才写入
boolean isValidStatus = true;
for (String validCode : VALID_FUNC_CODES) {
if (!payloadObj.containsKey(validCode)) {
isValidStatus = false;
// log.debug("【设备状态包】结构不合法非8个功能码跳过Redis写入deviceId={}payload={}", deviceId, payload);
break;
}
}
if (mqttAutoOffManager.hasAutoOffTask(deviceId) && isValidStatus) {
// ✅ 8个功能码状态包无条件写device:latest:{deviceId},避免自动关读不到最新状态
stringRedisTemplate.opsForValue().set(
"device:latest:" + deviceId,
payload, // 完整的8功能码JSON
latestTtlSeconds,
TimeUnit.SECONDS
);
// log.debug("【设备状态包】写入Redis成功deviceId={}", deviceId);
}
}
} catch (MqttException e) {
WxUtil.pushText(
"【消息转发失败】\n deviceId: "+deviceId+"\n payload: "+payload+"\n cause: "+e);
log.error("【消息转发失败】deviceId={}, msg={}", deviceId, e.getMessage(), e);
}
}
private String extractDeviceId(String topic) {
int first = topic.indexOf('/');
if (first < 0) return null;
int second = topic.indexOf('/', first + 1);
if (second < 0) return null;
return topic.substring(first + 1, second);
}
private boolean isJsonObjectLike(String s) {
if (s == null) return false;
int n = s.length();
for (int i = 0; i < n; i++) {
char c = s.charAt(i);
if (!Character.isWhitespace(c)) return c == '{';
}
return false;
}
public static boolean isAllKeysDigit(JSONObject obj) {
return obj != null && !obj.isEmpty()
&& obj.keySet().stream().allMatch(k -> k.matches("\\d+"));
}
}

View File

@ -0,0 +1,141 @@
package com.agri.framework.interceptor;
import com.agri.framework.config.MqttConfig;
import com.agri.system.domain.SysRollerAir;
import com.agri.system.service.*;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import org.apache.commons.lang3.ObjectUtils;
import org.checkerframework.checker.units.qual.A;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
public class FrontendConfigHandler {
private static final Logger log = LoggerFactory.getLogger(FrontendConfigHandler.class);
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private MqttConfig.MqttMessageSender mqttMessageSender;
@Autowired
private ISysRollerAirService sysRollerAirService;
@Value("${spring.mqtt.dtu-ctl-lock-ttl}")
private int dtuCtlLockTTL;
@Value("${agri.per-lap.len}")
private BigDecimal perLapLen;
@Value("${agri.per-lap.sec}")
private BigDecimal perLapSec;
/**
* ++
*/
public void handle(String topic, String payload) throws MqttException {
// 解析前端clientId、设备ID
String[] parts = topic.split("/");
String clientId = parts[1];
String deviceId = parts[2];
// 新增入参非空校验JDK 8兼容
if (!StringUtils.hasText(clientId) || !StringUtils.hasText(deviceId)) {
log.error("【指令处理】clientId或deviceId为空topic={}", topic);
return;
}
// 解析功能码({"功能码":状态码}格式)
Map<String, Integer> funcCodeMap = null;
try {
funcCodeMap = JSON.parseObject(payload, new TypeReference<Map<String, Integer>>() {
});
} catch (Exception e) {
log.error("【指令处理】功能码解析失败payload={}", payload, e);
return;
}
if (funcCodeMap == null || funcCodeMap.isEmpty()) {
log.warn("【指令处理】前端{}操作设备{}失败:功能码为空", clientId, deviceId);
return;
}
// 提取第一个功能码作为锁标识
String funcType = funcCodeMap.keySet().iterator().next();
// 2. 分布式锁设备ID+功能类型(避免同设备同功能并发控制)
String lockKey = "lock:" + deviceId + ":" + funcType;
Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent(
lockKey, clientId, dtuCtlLockTTL, TimeUnit.SECONDS // 延长至15秒适配设备回执场景
);
if (lockSuccess == null || !lockSuccess) {
String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
mqttMessageSender.publish(errorTopic, "{\"msg\":\"设备" + funcType + "功能忙,请稍后重试\",\"clientId\":\""+clientId+"\"}");
log.warn("【分布式锁】前端{}操作设备{}的{}功能失败;可能其他用户正在操作此功能", clientId, deviceId, funcType);
return;
}
try {
// 转发前端指令
String deviceTopic = "dtu/" + deviceId + "/down";
mqttMessageSender.publish(deviceTopic, payload);
LocalDateTime currentTime = LocalDateTime.now();
// 3. 记录日志
log.info("【指令处理】前端{}于{}控制设备{}的{}功能,指令:{}",
clientId, currentTime, deviceId, funcType, payload);
String funcName = funcType.substring(0, funcType.length() - 1);
Integer funcCode = funcCodeMap.get(funcType);
// 当卷膜 开 暂停,才执行的逻辑
if (funcCode == 0 && funcType.contains("k")) {
SysRollerAir sysRollerAir = sysRollerAirService.lambdaQuery()
.eq(SysRollerAir::getImei, deviceId)
.eq(SysRollerAir::getRoller, funcName)
.eq(SysRollerAir::getOpType, 1)
.orderByDesc(SysRollerAir::getId)
.last("limit 1")
.one();
BigDecimal ventTotalLen = BigDecimal.ZERO;
if (ObjectUtils.isNotEmpty(sysRollerAir)
&& sysRollerAir.getOpTime().isBefore(currentTime)) {
long time = Math.abs(Duration.between(currentTime, sysRollerAir.getOpTime()).getSeconds());
BigDecimal ventTotalTime = BigDecimal.valueOf(time);
// 除以一圈的时间乘以一圈的长度
ventTotalLen = ventTotalTime.divide(perLapSec, 2, RoundingMode.HALF_UP)
.multiply(perLapLen).setScale(2, RoundingMode.HALF_UP);
log.info("【自动化参数】卷膜校准时间:{}; 一圈秒数:{}; 一圈长度: {}", ventTotalTime,perLapSec,perLapLen);
}
String config = "{\"ventTotalLen\": " + ventTotalLen +",\"clientId\":\""+clientId+"\"}";
// 查数据库、最后一条卷膜开暂停。计算时间发送自动关时间
mqttMessageSender.publish("frontend/"+clientId+"/dtu/"+deviceId+"/config", config);
}
// 插入记录
SysRollerAir rollerAir = new SysRollerAir();
rollerAir.setImei(deviceId);
rollerAir.setRoller(funcName);
rollerAir.setOpType(funcCode);
rollerAir.setPayload(payload);
rollerAir.setClientid(clientId);
rollerAir.setOpTime(currentTime);
sysRollerAirService.save(rollerAir);
log.info("【指令转发】前端{} → 设备{}的{}功能", clientId, deviceId, funcType);
} finally {
stringRedisTemplate.delete(lockKey);
}
}
}

View File

@ -0,0 +1,277 @@
package com.agri.framework.interceptor;
import com.agri.common.core.domain.entity.SysUser;
import com.agri.common.utils.wechat.WxUtil;
import com.agri.framework.config.MqttConfig;
import com.agri.framework.manager.MqttAutoOffManager;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysAgriLimit;
import com.agri.system.domain.SysDevOperLog;
import com.agri.system.domain.SysMessage;
import com.agri.system.mapper.SysUserMapper;
import com.agri.system.service.ISysAgriInfoService;
import com.agri.system.service.ISysAgriLimitService;
import com.agri.system.service.ISysDevOperLogService;
import com.agri.system.service.ISysUserService;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
*
*
* 1.
* 2.
* 3.
* 4.
* JDK 8
*/
@Component
public class FrontendControlHandler {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(FrontendControlHandler.class);
/**
* Redis线
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* MQTTMqttConfig
*/
@Resource
private MqttConfig.MqttMessageSender mqttMessageSender;
@Resource
private MqttAutoOffManager mqttAutoOffManager;
@Autowired
private ISysAgriLimitService agriLimitService;
@Autowired
private ISysAgriInfoService sysAgriInfoService;
@Autowired
private ISysDevOperLogService sysDevOperLogService;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Autowired
private ISysUserService sysUserService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Value("${spring.mqtt.dtu-ctl-lock-ttl}")
private int dtuCtlLockTTL;
private static final Map<String, Function<SysAgriLimit, Integer>> LIMIT_MAP = new HashMap<>();
static {
LIMIT_MAP.put("jm1g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm1gLimit())));
LIMIT_MAP.put("jm2g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm2gLimit())));
LIMIT_MAP.put("jbg1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJbgLimit())));
LIMIT_MAP.put("jm3g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm3gLimit())));
LIMIT_MAP.put("jm2k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm2kLimit())));
LIMIT_MAP.put("jm3k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm3kLimit())));
LIMIT_MAP.put("jbk1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJbkLimit())));
LIMIT_MAP.put("jm1k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm1kLimit())));
}
/**
* ++
*/
public void handle(String topic, String payload) throws MqttException {
// 解析前端clientId、设备ID
String[] parts = topic.split("/");
String clientId = parts[1];
String deviceId = parts[3];
// 新增入参非空校验JDK 8兼容
if (!StringUtils.hasText(clientId) || !StringUtils.hasText(deviceId)) {
log.error("【指令处理】clientId或deviceId为空topic={}", topic);
return;
}
// 1. 权限校验示例admin开头有全权限
if (!checkPermission(clientId, deviceId)) {
String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
mqttMessageSender.publish(errorTopic, "{\"msg\":\"无设备操作权限\"}");
log.warn("【权限校验】前端{}操作设备{}失败", clientId, deviceId);
return;
}
// 4. 转发指令到设备
String deviceTopic = "dtu/" + deviceId + "/down";
JSONObject payloadObj;
try {
payloadObj = JSON.parseObject(payload);
} catch (Exception e) {
log.error("【设备处理】JSON解析失败payload={}", payload, e);
return;
}
if (payloadObj.containsKey("read")) {
mqttMessageSender.publish(deviceTopic, payload);
log.info("【主动读取】设备{}主动读取payload={}",deviceId, payload);
return;
}
// 解析功能码({"功能码":状态码}格式)
Map<String, Integer> funcCodeMap = null;
try {
funcCodeMap = JSON.parseObject(payload, new TypeReference<Map<String, Integer>>() {
});
} catch (Exception e) {
log.error("【指令处理】功能码解析失败payload={}", payload, e);
String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
mqttMessageSender.publish(errorTopic, "{\"msg\":\"指令格式错误\"}");
return;
}
if (funcCodeMap == null || funcCodeMap.isEmpty()) {
String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
mqttMessageSender.publish(errorTopic, "{\"msg\":\"功能码不能为空\"}");
log.warn("【指令处理】前端{}操作设备{}失败:功能码为空", clientId, deviceId);
return;
}
// 提取第一个功能码作为锁标识
String funcType = funcCodeMap.keySet().iterator().next();
// 2. 分布式锁设备ID+功能类型(避免同设备同功能并发控制)
String lockKey = "lock:" + deviceId + ":" + funcType;
Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent(
lockKey, clientId, dtuCtlLockTTL, TimeUnit.SECONDS // 延长至15秒适配设备回执场景
);
if (lockSuccess == null || !lockSuccess) {
String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener";
mqttMessageSender.publish(errorTopic, "{\"msg\":\"设备" + funcType + "功能忙,请稍后重试\",\"clientId\":\""+clientId+"\"}");
log.warn("【分布式锁】前端{}操作设备{}的{}功能失败;可能其他用户正在操作此功能", clientId, deviceId, funcType);
return;
}
try {
// 3. 记录日志
log.info("【指令处理】前端{}于{}控制设备{}的{}功能,指令:{}",
clientId, LocalDateTime.now(), deviceId, funcType, payload);
SysUser sysUser = sysUserService.lambdaQuery()
.eq(SysUser::getClientId, clientId)
.one();
String operator = "手动控制";
if (sysUser!=null) {
operator = sysUser.getUserName();
}
SysAgriInfo agriInfo = sysAgriInfoService.lambdaQuery()
.eq(SysAgriInfo::getImei, deviceId)
.one();
String agriName = (agriInfo!=null && ObjectUtils.isNotEmpty(agriInfo.getAgriName()))?agriInfo.getAgriName():null;
SysDevOperLog logDto = new SysDevOperLog();
logDto.setAgriName(agriName);
logDto.setImei(deviceId);
logDto.setFuncCode(funcType);
logDto.setOpType(funcCodeMap.get(funcType));
logDto.setOpSource(1);
logDto.setPayload(payload);
logDto.setLockAcquired(1);
logDto.setLockHolder(clientId);
logDto.setCreateBy(operator);
sysDevOperLogService.save(logDto);
//todo
mqttMessageSender.publish(deviceTopic, payload);
// if (save) {
// testAutoOffTask(deviceId,funcCodeMap);
// }
log.info("【指令转发】前端{} → 设备{}的{}功能", clientId, deviceId, funcType);
} finally {
stringRedisTemplate.delete(lockKey);
}
}
public void testAutoOffTask(String deviceId, Map<String,Integer> funcCodeMap) throws MqttException {
String funcType = funcCodeMap.keySet().iterator().next();
Integer funcValue = funcCodeMap.get(funcType);
// 释放对应功能的分布式锁
String lockKey = "lock:" + deviceId + ":" + funcType;
Boolean delete = stringRedisTemplate.delete(lockKey);
if (delete) {
log.info("【设备控制锁删除成功!】");
}
// 回执成功且值=1时启动自动关闭任务保留原有逻辑
if (StringUtils.hasText(funcType) && funcValue != null && funcValue == 1) {
SysAgriLimit agriLimit = agriLimitService.lambdaQuery()
.eq(SysAgriLimit::getImei, deviceId)
.one();
int autoOffSeconds = 0;
if (agriLimit != null) {
autoOffSeconds = LIMIT_MAP.getOrDefault(funcType, k -> 0).apply(agriLimit);
}
// 新增:判断是否真的需要执行自动关任务(延迟秒数>0才是有效任务
if (autoOffSeconds > 0) {
mqttAutoOffManager.scheduleAutoOff(deviceId, funcType, autoOffSeconds);
log.debug("【自动关任务】标记需要执行deviceId={}, funcType={}, delay={}s", deviceId, funcType, autoOffSeconds);
} else {
log.debug("【自动关任务】标记不符合执行运行时间未配置deviceId={}, funcType={}, delay={}s", deviceId, funcType, autoOffSeconds);
}
sysDevOperLogService.lambdaUpdate()
.eq(SysDevOperLog::getImei, deviceId)
.eq(SysDevOperLog::getFuncCode, funcType)
.eq(SysDevOperLog::getOpType, funcValue)
.eq(SysDevOperLog::getLockAcquired,1)
.orderByDesc(SysDevOperLog::getCreateTime)
.last("LIMIT 1")
.set(SysDevOperLog::getAckReceived,1)
.set(SysDevOperLog::getIsLockSuc,1)
.set(SysDevOperLog::getAckSuc, 1)
.set(SysDevOperLog::getIsTask, 1)
.set(autoOffSeconds <= 0, SysDevOperLog::getNoTaskReason,"当前运行时间:【"+autoOffSeconds+"】")
.set(SysDevOperLog::getUpdateBy, "测试")
.set(SysDevOperLog::getExecResult, 1)
.update();
}
if (StringUtils.hasText(funcType) && funcValue != null && funcValue == 0) {
mqttAutoOffManager.cancelAutoOff(deviceId, funcType);
}
}
/**
*
*
* 1. clientIdadmin_
* 2. Redisuser_device:{clientId} deviceId
*
* @param clientId
* @param deviceId ID
* @return true=false=
*/
private boolean checkPermission(String clientId, String deviceId) {
// 管理员权限clientId以admin_开头
// 普通用户权限校验Redis中是否绑定该设备
return Boolean.TRUE;
}
public void sendAlarmMessage(List<SysMessage> messages) throws Exception {
if (CollectionUtils.isEmpty(messages)) {
return;
}
for (SysMessage message : messages) {
Map<String, Object> alarmMsg = new HashMap<>();
alarmMsg.put("msgType", message);
String alarmMessage = objectMapper.writeValueAsString(alarmMsg);
mqttMessageSender.publish("device/" + message.getImei() + "/alarm", alarmMessage);
}
}
}

View File

@ -0,0 +1,61 @@
package com.agri.framework.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* 线
*
* 1. 线
* 2. TTL退
* JDK 8
*/
@Component
public class FrontendOnlineHandler {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(FrontendOnlineHandler.class);
/**
* Redis线
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
// 新增前端订阅关系TTL兜底“取消订阅失败/异常退出”——只维护subc:{clientId}的TTL
@Value("${spring.mqtt.subc-ttl-seconds:3600}")
private int subcTtlSeconds;
/**
* 线Redis线TTL
* frontend/{clientId}/online
*/
public void handle(String topic, String payload) {
try {
String[] parts = topic.split("/");
if (parts.length < 3) {
return;
}
String clientId = parts[1];
if (!StringUtils.hasText(clientId)) {
return;
}
// 续期subc:{clientId}
stringRedisTemplate.expire("subc:" + clientId, subcTtlSeconds, TimeUnit.SECONDS);
// todo 生产环境不建议打印每次心跳
// log.debug("【在线心跳】clientId={} 续期subcTTL={}s payload={}", clientId, subcTtlSeconds, payload);
} catch (Exception e) {
log.warn("【在线心跳】处理失败 topic={} msg={}", topic, e.getMessage());
}
}
}

View File

@ -0,0 +1,145 @@
package com.agri.framework.manager;
import com.agri.framework.config.MqttConfig;
import com.agri.framework.interceptor.FrontendControlHandler;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.service.AgriService;
import com.agri.system.service.ISysAgriInfoService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class AgriStatusManager {
private static final Logger log = LoggerFactory.getLogger(AgriStatusManager.class);
// Redis 前缀常量
private static final String SUB_KEY_PREFIX = "sub:";
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private MqttConfig.MqttMessageSender mqttMessageSender;
// JSON序列化工具单例
private final ObjectMapper objectMapper = new ObjectMapper();
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Resource
private FrontendControlHandler frontendControlHandler;
@Autowired
private AgriService agriService;
// ========== 批量查在线状态Pipeline 优化版JDK 8 适配) ==========
// 在线离线的都得推
public Map<String, Map<String, Boolean>> batchCheckDeviceOnline(List<String> imeiList) {
Map<String, Map<String, Boolean>> result = new HashMap<>();
if (imeiList.isEmpty()) {
return result;
}
// JDK 8 显式声明 RedisCallback避免 Lambda 泛型问题
List<Object> results = stringRedisTemplate.executePipelined(
new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
StringRedisSerializer serializer = new StringRedisSerializer();
for (String imei : imeiList) {
byte[] onlineKeyBytes = serializer.serialize(SUB_KEY_PREFIX + imei);
connection.exists(onlineKeyBytes); // 批量执行 exists
connection.exists(serializer.serialize(imei));
}
return null;
}
},
new StringRedisSerializer()
);
// 解析结果每两个结果对应一个IMEIsubExist + imeiOnline
for (int i = 0; i < imeiList.size(); i++) {
String imei = imeiList.get(i);
// 初始化默认状态:不存在+离线
boolean subExist = false;
boolean imeiOnline = false;
// 越界判断避免IndexOutOfBoundsException
int subIndex = i * 2;
int imeiIndex = i * 2 + 1;
if (subIndex < results.size()) {
Object subResult = results.get(subIndex);
subExist = parseExistsResult(subResult);
}
if (imeiIndex < results.size()) {
Object imeiResult = results.get(imeiIndex);
imeiOnline = parseExistsResult(imeiResult);
}
result.put(imei, ImmutableMap.of("subExist", subExist, "imeiOnline", imeiOnline));
}
return result;
}
private boolean parseExistsResult(Object result) {
if (result instanceof Long) {
return ((Long) result) == 1;
} else if (result instanceof Boolean) {
return (Boolean) result;
}
return false;
}
// ========== 核心方法3异步批量推送在线状态到 MQTT线程池隔离 ==========
@Async("mqttPushExecutor")
public void asyncBatchPushMqtt(Map<String, Map<String, Boolean>> statusMap) {
if (statusMap.isEmpty()) {
log.info("不存在任何imei");
return;
}
int successCount = 0;
int failCount = 0;
String dateNow = LocalDateTime.now().format(DATE_TIME_FORMATTER);
// 在线状态
for (Map.Entry<String, Map<String, Boolean>> map : statusMap.entrySet()) {
String imei = map.getKey();
try {
// 按你的需求,直接推送到 frontend/{imei}/online 主题
Map<String, Boolean> imeiMap = map.getValue();
// 设备在线的 && 推送首页状态 离线在线都推
if (imeiMap.get("subExist")) {
// 构造首页消息用ObjectMapper序列化避免手动拼接JSON
Map<String, Object> onlineMsg = new HashMap<>();
onlineMsg.put("online", imeiMap.get("imeiOnline") ? "在线" : "离线");
onlineMsg.put("time", dateNow); // 毫秒时间戳
onlineMsg.put("imei", imei);
String onlineMessage = objectMapper.writeValueAsString(onlineMsg);
mqttMessageSender.publish("device/" + imei + "/status", onlineMessage);
}
successCount++;
} catch (Exception e) {
failCount++;
log.error("向设备 {} 推送在线状态失败", imei, e);
}
}
log.info("批量在线状态推送完成:成功={},失败={}", successCount, failCount);
}
}

View File

@ -0,0 +1,407 @@
package com.agri.framework.manager;
import com.agri.common.utils.wechat.WxUtil;
import com.agri.framework.config.MqttConfig;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysDevOperLog;
import com.agri.system.service.ISysAgriInfoService;
import com.agri.system.service.ISysDevOperLogService;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.lang3.ObjectUtils;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
*
*
* 1. /线
* 2. /
* 3.
* 4.
* 线
* JDK 8
*/
@Component
public class MqttAutoOffManager {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(MqttAutoOffManager.class);
/**
* Redis线
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* MQTTMqttConfig
*/
@Resource
private MqttConfig.MqttMessageSender mqttMessageSender;
// 改造:将单线程池改为固定线程池,支持多任务并行执行
// 替代原有的 Executors.newSingleThreadScheduledExecutor()
private ScheduledExecutorService autoOffExecutor;
// 新增:同设备同功能只保留最后一次自动关任务
private final ConcurrentHashMap<String, ScheduledFuture<?>> autoOffFutureMap = new ConcurrentHashMap<>();
// 新增按设备维度统计“未完成的自动关任务”数量hasAutoOffTask从扫描O(N)降为O(1)
private final ConcurrentHashMap<String, Integer> autoOffDeviceCnt = new ConcurrentHashMap<>();
// 新增:自动关闭任务线程池核心线程数(可配置)
@Value("${spring.mqtt.auto-off-thread-pool-size:5}")
private int autoOffThreadPoolSize;
@Value("${spring.mqtt.dtu-ctl-lock-ttl}")
private int dtuCtlLockTTL;
@Autowired
private ISysAgriInfoService sysAgriInfoService;
@Autowired
private ISysDevOperLogService sysDevOperLogService;
/**
* 线
* @param corePoolSize 线
*/
public void initExecutor(int corePoolSize) {
// 初始化多线程池(固定线程数)
autoOffExecutor = new ScheduledThreadPoolExecutor(
autoOffThreadPoolSize, // 核心线程数
r -> {
Thread thread = new Thread(r);
thread.setName("auto-off-task-" + thread.getId());
thread.setDaemon(true); // 设置为守护线程不阻塞JVM退出
return thread;
},
new ThreadPoolExecutor.CallerRunsPolicy() // 队列压力或关闭时兜底不丢任务
);
// 关键优化1取消任务后立即从队列移除避免队列堆积
((ScheduledThreadPoolExecutor) autoOffExecutor).setRemoveOnCancelPolicy(true);
// 关键优化2允许核心线程超时回收空闲时省资源
((ScheduledThreadPoolExecutor) autoOffExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
((ScheduledThreadPoolExecutor) autoOffExecutor).allowCoreThreadTimeOut(true);
log.info("自动关任务线程池初始化完成,核心线程数={}", corePoolSize);
}
/**
* 线
*/
public void shutdownExecutor() {
try {
// 1. 取消所有未执行的自动关闭任务
for (Map.Entry<String, ScheduledFuture<?>> entry : autoOffFutureMap.entrySet()) {
entry.getValue().cancel(false);
log.debug("【自动关任务】取消任务:{}", entry.getKey());
}
autoOffFutureMap.clear();
// ✅ 停止时直接清空计数,避免残留
autoOffDeviceCnt.clear();
// 2. 优雅关闭线程池
if (autoOffExecutor != null) {
autoOffExecutor.shutdown();
try {
// 等待3秒让任务完成
if (!autoOffExecutor.awaitTermination(3, TimeUnit.SECONDS)) {
// 强制关闭
autoOffExecutor.shutdownNow();
log.warn("【自动关任务】线程池强制关闭");
}
} catch (InterruptedException e) {
autoOffExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
log.info("【自动关任务】线程池已关闭");
}
} catch (Exception e) {
log.error("【自动关任务】线程池关闭失败", e);
}
}
// 新增:是否存在该设备的自动关任务
public boolean hasAutoOffTask(String deviceId) {
if (!StringUtils.hasText(deviceId)) {
return false;
}
// ✅ O(1)查询,无需扫描整个任务表
Integer cnt = autoOffDeviceCnt.get(deviceId);
return cnt != null && cnt > 0;
}
// 新增:自动关任务计数 +1只维护deviceId维度确保hasAutoOffTask为O(1)
private void incAutoOffCnt(String deviceId) {
if (!StringUtils.hasText(deviceId)) {
return;
}
autoOffDeviceCnt.merge(deviceId, 1, (a, b) -> a + b);
}
// 新增:自动关任务计数 -1避免负数归零则清理key省内存
private void decAutoOffCnt(String deviceId) {
if (!StringUtils.hasText(deviceId)) {
return;
}
autoOffDeviceCnt.compute(deviceId, (k, v) -> {
if (v == null || v <= 1) {
return null;
}
return v - 1;
});
}
// 改造:多线程执行自动关闭任务
// 起个任务,固定多少秒-n秒【监听最新的设备状态如果还在运行】发送设备关的指令
public void scheduleAutoOff(String deviceId, String funcType, int delaySeconds) {
// ✅ 防御避免极端情况下线程池尚未初始化导致NPE
if (autoOffExecutor == null) {
log.warn("【自动关任务】线程池未初始化跳过创建任务deviceId={}, funcType={}", deviceId, funcType);
return;
}
String taskKey = "autooff:" + deviceId + ":" + funcType;
cancelAutoOff(deviceId,funcType);
// 使用多线程池提交任务
ScheduledFuture<?> newFuture = autoOffExecutor.schedule(() -> {
try {
runAutoOff(deviceId, funcType);
} catch (Exception e) {
WxUtil.pushText("【自动关任务】提交任务失败! \n deviceId: "+deviceId+"\n funcType: "+funcType+"\n cause: "+e);
log.error("【自动关任务】执行失败deviceId={}, funcType={}", deviceId, funcType, e);
} finally {
// 任务执行完成后移除映射
autoOffFutureMap.remove(taskKey);
// ✅ 任务结束(成功/失败都算结束减少该设备的“未完成任务数”保证hasAutoOffTask准确
decAutoOffCnt(deviceId);
}
}, delaySeconds, TimeUnit.SECONDS);
// 保存新任务的引用
autoOffFutureMap.put(taskKey, newFuture);
// ✅ 新任务创建成功:增加该设备的“未完成任务数”
incAutoOffCnt(deviceId);
log.info("【当前任务队列】:{}",autoOffDeviceCnt);
log.info("【自动关任务】已创建多线程deviceId={}, funcType={}, delay={}s", deviceId, funcType, delaySeconds);
}
// 自动关闭任务的核心逻辑(无改动)
// 新增读取最新状态device:latest:{deviceId}若仍为1则下发 {"funcType":0} 到 dtu/{id}/down
private void runAutoOff(String deviceId, String funcType) throws MqttException {
String latest = stringRedisTemplate.opsForValue().get("device:latest:" + deviceId);
String skipReason = "";
if (!StringUtils.hasText(latest)) {
//todo
log.warn("【自动关任务】无最新状态跳过deviceId={}, funcType={}", deviceId, funcType);
return;
}
JSONObject latestObj;
try {
latestObj = JSON.parseObject(latest);
} catch (Exception e) {
WxUtil.pushText("自动关任务执行报错-解析异常:\n deviceId: " + deviceId + "\n funcType:" + funcType+"\n Cause: "+e);
log.warn("【自动关任务】最新状态JSON解析失败跳过deviceId={}, funcType={}", deviceId, funcType);
return;
}
if (latestObj == null || latestObj.isEmpty()) {
log.info("【自动关任务】最新状态为空");
skipReason = "【自动关任务】最新状态为空";
return;
}
// 设备每10秒上报的状态包{"jm1k":0/1,...} 顶层字段直接取
Integer current = null;
String key = getKey(funcType);
try {
if (latestObj.containsKey(key)) {
current = latestObj.getIntValue(key);
}
} catch (Exception ignore) {
skipReason = "【自动关任务】最新状态功能码获取失败";
}
if (current == null) {
skipReason = "【自动关任务】状态未知";
}
if (current != 1) {
skipReason = "【自动关任务】未运行";
}
sysDevOperLogService.lambdaUpdate()
.eq(SysDevOperLog::getImei, deviceId)
.eq(SysDevOperLog::getFuncCode, funcType)
.eq(SysDevOperLog::getOpType, 1)
.eq(SysDevOperLog::getOpSource, 1)
.eq(SysDevOperLog::getAckSuc, 1)
.eq(SysDevOperLog::getIsTask, 1)
.orderByDesc(SysDevOperLog::getCreateTime)
.last("LIMIT 1")
.set(SysDevOperLog::getExecResult,skipReason.isEmpty()?1:0)
.set(SysDevOperLog::getUpdateBy,"自动关更新数据")
.set(SysDevOperLog::getSkipReason, skipReason.isEmpty()?"执行成功":skipReason)
.update();
if (!skipReason.isEmpty()) {
log.info("【自动关任务】检测未运行或状态未知跳过关闭deviceId={}, funcType={}, current={}", deviceId, funcType, current);
return;
}
// 新增:自动关也走分布式锁(避免与前端并发控制同一功能导致乱序/互相覆盖)
String lockKey = "lock:" + deviceId + ":" + funcType;
Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent(
lockKey, "autooff", dtuCtlLockTTL, TimeUnit.SECONDS
);
if (lockSuccess == null || !lockSuccess) {
log.info("【自动关任务】{}功能忙锁占用跳过自动关闭deviceId={}, funcType={}", funcType, deviceId, funcType);
return;
}
try {
String deviceTopic = "dtu/" + deviceId + "/down";
JSONObject down = new JSONObject();
down.put(funcType, 0);
log.info("触发自动化条件");
mqttMessageSender.publish(deviceTopic, down.toJSONString());
saveDevLog(deviceId, funcType, down, latest);
log.info("【自动关任务】检测仍在运行已下发关闭deviceId={}, funcType={}, payload={}", deviceId, funcType, down.toJSONString());
} catch (Exception e){
WxUtil.pushText("自动关任务执行报错-下发关闭指令:\n deviceId: " + deviceId + "\n funcType:" + funcType+"\n Cause: "+e);
log.warn("【自动关任务】下发关闭指令失败跳过deviceId={}, funcType={}", deviceId, funcType);
} finally {
stringRedisTemplate.delete(lockKey);
}
}
private void saveDevLog(String deviceId, String funcType, JSONObject down, String latest) {
SysAgriInfo agriInfo = sysAgriInfoService.lambdaQuery()
.eq(SysAgriInfo::getImei, deviceId)
.one();
String agriName = (agriInfo != null && ObjectUtils.isNotEmpty(agriInfo.getAgriName())) ? agriInfo.getAgriName() : null;
SysDevOperLog logDto = new SysDevOperLog();
logDto.setAgriName(agriName);
logDto.setImei(deviceId);
logDto.setFuncCode(funcType);
logDto.setOpType(0);
logDto.setOpSource(2);
logDto.setPayload(down.toJSONString());
logDto.setLockAcquired(1);
logDto.setLockHolder("autoOff");
logDto.setExecResult(1);
logDto.setLatestState(latest);
logDto.setCreateBy("自动关");
logDto.setTaskStatus(getFutureStatus().toString());
sysDevOperLogService.save(logDto);
}
// 新增:收到“关”指令时,尝试取消对应自动关任务(优化:减少无意义任务执行;正确性仍以到点状态判断为准)
public void cancelAutoOff(String deviceId, String funcType) {
if (!StringUtils.hasText(deviceId) || !StringUtils.hasText(funcType)) {
return;
}
String taskKey = "autooff:" + deviceId + ":" + funcType;
// 同设备同功能只保留最后一次任务:只有旧任务还没开始时才替换
ScheduledFuture<?> oldFuture = autoOffFutureMap.get(taskKey);
if (oldFuture != null) {
log.info("存在定时任务taskKey: "+taskKey);
// cancel=false 说明任务已开始/已完成,避免双执行:不再创建新任务
if (!oldFuture.cancel(false)) {
return;
}
sysDevOperLogService.lambdaUpdate()
.eq(SysDevOperLog::getImei, deviceId)
.eq(SysDevOperLog::getFuncCode, funcType)
.eq(SysDevOperLog::getOpType, 1)
.eq(SysDevOperLog::getOpSource, 1)
.eq(SysDevOperLog::getAckSuc, 1)
.eq(SysDevOperLog::getIsTask, 1)
.orderByDesc(SysDevOperLog::getCreateTime)
.last("LIMIT 2")
.set(SysDevOperLog::getExecResult,2)
.set(SysDevOperLog::getLatestState, "{\"msg\":\"任务已退出\"}")
.set(SysDevOperLog::getUpdateBy,"自动关")
.set(SysDevOperLog::getSkipReason, "旧任务已退出")
.update();
// cancel成功旧任务不会跑了这时再remove并减计数
autoOffFutureMap.remove(taskKey, oldFuture);
decAutoOffCnt(deviceId);
log.info("旧任务已退出:"+taskKey);
} else {
log.info("不存在定时任务taskKey: "+taskKey);
}
}
/**
* imei
*
* 105300
*
* @param deviceId
* @param funcType
* @return
*/
public boolean isHaveTask(String deviceId, String funcType) {
String taskKey = "autooff:" + deviceId + ":" + funcType;
// 同设备同功能只保留最后一次任务:只有旧任务还没开始时才替换
ScheduledFuture<?> oldFuture = autoOffFutureMap.get(taskKey);
return oldFuture != null;
}
// 自动关是否启用(你可以先写死 true / false
public boolean isEnabled() {
return true; // 之后可接配置
}
// 线程池是否初始化
public boolean isExecutorInited() {
return autoOffExecutor != null;
}
// 当前所有未完成任务数
public int getTotalTaskCount() {
return autoOffFutureMap.size();
}
// 当前有自动关任务的设备数量
public int getDeviceTaskCount() {
return autoOffDeviceCnt.size();
}
public JSONObject getFutureStatus() {
JSONObject json = new JSONObject();
json.put("isEnabled", true);
json.put("isExecutorInited", autoOffExecutor != null);
json.put("getTotalTaskCount", autoOffFutureMap.size());
json.put("getDeviceTaskCount", autoOffDeviceCnt.size());
return json;
}
public String getKey(String funcType) {
// 边界判断:避免空指针异常和字符串长度不足的异常
if (funcType == null || funcType.length() <= 1) {
return funcType == null ? null : "";
}
// 截取从索引0开始到倒数第二位结束substring的结束索引是开区间不包含自身
return funcType.substring(0, funcType.length() - 1);
}
}

View File

@ -0,0 +1,345 @@
package com.agri.framework.manager;
import com.agri.common.utils.wechat.WxUtil;
import com.agri.framework.web.dispatcher.MqttMessageDispatcher;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* MQTT
*
* 1.
* 2. MQTT//
* 3. 线
* 4. MQTT
* JDK 8
*/
@Component
public class MqttClientManager implements SmartLifecycle {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(MqttClientManager.class);
/**
* MQTT/
*/
private final AtomicBoolean isRunning = new AtomicBoolean(false);
/**
* MQTTMqttConfig
*/
@Resource
private MqttClient mqttClient;
/**
* MQTTMqttConfig
*/
@Resource
private MqttConnectOptions mqttConnectOptions;
/**
* MQTT
*/
@Resource
private MqttMessageDispatcher mqttMessageDispatcher;
/**
* /线
*/
@Resource
private MqttAutoOffManager mqttAutoOffManager;
// 读取配置文件中的默认订阅主题(移除心跳主题)
@Value("${spring.mqtt.default-topic}")
private String defaultTopic;
// 新增:自动关闭任务线程池核心线程数(可配置)
@Value("${spring.mqtt.auto-off-thread-pool-size:5}")
private int autoOffThreadPoolSize;
private final ThreadPoolExecutor mqttBizPool =
new ThreadPoolExecutor(
8, // core
16, // max
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2000), // 有界、较小
r -> {
Thread t = new Thread(r);
t.setName("mqtt-biz-" + t.getId());
t.setDaemon(true);
return t;
},
new ThreadPoolExecutor.DiscardPolicy() // 直接丢
);
/**
* +
* @PostConstructSmartLifecyclestart()
* <p>
* APahoconnectOptions.setAutomaticReconnect(true)
*/
public void subscribeTopics() throws MqttException {
// 关键补充1判空
if (mqttClient == null) {
log.error("【MQTT初始化】客户端实例为空无法订阅主题");
throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
}
// 解析配置的主题列表
final String[] topicsFinal = Arrays.stream(defaultTopic.split(","))
.map(String::trim).toArray(String[]::new);
final int[] qosFinal = new int[topicsFinal.length];
Arrays.fill(qosFinal, 0);
// 设置MQTT消息回调处理连接断开、消息接收、消息发布完成
mqttClient.setCallback(new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
log.info("【MQTT连接完成】reconnect={}, serverURI={}, clientId={}",
reconnect, serverURI, safeClientId());
// cleanSession=true重连后必须补订阅
if (reconnect) {
try {
mqttClient.subscribe(topicsFinal, qosFinal);
log.info("【MQTT订阅恢复】topicsFinal={}", String.join(",", topicsFinal));
} catch (Exception e) {
log.error("【MQTT订阅恢复失败】", e);
}
}
}
/**
* MQTT
* @param cause
*/
@Override
public void connectionLost(Throwable cause) {
log.info("autoReconnect={}, cleanSession={}, keepAlive={}",
mqttConnectOptions.isAutomaticReconnect(),
mqttConnectOptions.isCleanSession(),
mqttConnectOptions.getKeepAliveInterval());
log.error("【MQTT连接异常】连接断开clientId{},原因:{}",
safeClientId(), (cause == null ? "unknown" : cause.getMessage()), cause);
WxUtil.pushText("【MQTT连接异常】连接断开\n clientId"+safeClientId()+"\n Cause: "+cause);
// 【方案A】不再触发自写重连Paho自动重连会接管重连过程
// 这里只记录日志即可
if (isRunning.get()) {
log.warn("【MQTT自动重连】已开启automaticReconnect等待Paho自动重连...");
}
}
/**
* MQTT
* @param topic
* @param message
* @throws Exception
*/
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
final String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
if (message.isRetained()) {
log.info("ignore retained snapshot: {}", topic);
return;
}
mqttBizPool.execute(() -> {
// log.debug("mqttBizPool active={}, queue={}",
// mqttBizPool.getActiveCount(),
// mqttBizPool.getQueue().size());
if (mqttBizPool.getActiveCount()>10 || mqttBizPool.getQueue().size()>1000) {
WxUtil.pushText("线程池繁忙! \n 正在处理中任务:"+mqttBizPool.getActiveCount()+"\n 剩余待进行任务:"+mqttBizPool.getQueue().size());
}
try {
// 优化显式指定UTF-8编码避免乱码JDK 8兼容
mqttMessageDispatcher.handleMessage(topic, payload);
} catch (Exception e) {
log.error("【MQTT消息处理异常】topic={}, payload={}", topic, payload, e);
}
});
}
/**
*
* @param token
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
if (token != null && token.getTopics() != null && token.getTopics().length > 0) {
// log.info("【MQTT确认】消息发布完成clientId{},主题:{}", safeClientId(), token.getTopics()[0]);
}
}
});
if (!mqttClient.isConnected()) {
try {
// 使用注入的连接配置项连接Broker带用户名密码、自动重连等配置
mqttClient.connect(mqttConnectOptions);
log.info("【MQTT连接】客户端已成功连接到BrokerclientId{}", mqttClient.getClientId());
} catch (MqttException e) {
log.error("【MQTT连接】连接Broker失败clientId{}", mqttClient.getClientId(), e);
throw e;
}
}
// 订阅主题
// connect 后首次订阅
try {
mqttClient.subscribe(topicsFinal, qosFinal);
log.info("【MQTT初始化】订阅主题完成clientId{}topicsFinal={}",
mqttClient.getClientId(), String.join(",", topicsFinal));
} catch (MqttException e) {
log.error("【MQTT初始化】订阅失败clientId{}topicsFinal={}",
safeClientId(), String.join(",", topicsFinal), e);
throw e;
}
}
private String safeClientId() {
try {
return (mqttClient == null ? "null" : mqttClient.getClientId());
} catch (Exception e) {
return "unknown";
}
}
// ========== 手动重连接口供Controller调用 ==========
/**
* MQTTclientclient
*/
public synchronized String manualReconnect() {
isRunning.set(true);
try {
// 强制断开旧连接(如果存在)
if (mqttClient != null && mqttClient.isConnected()) {
mqttClient.disconnect();
}
// 重新初始化订阅内部会connect + subscribe
subscribeTopics();
log.info("【手动重连】MQTT客户端重连成功");
return "MQTT手动重连成功";
} catch (MqttException e) {
log.error("【手动重连】MQTT客户端重连失败", e);
return "MQTT手动重连失败" + e.getMessage();
}
}
/**
* MQTT
*/
public Map<String,Object> getMqttStatus() {
boolean connected = (mqttClient != null && mqttClient.isConnected());
String status = connected ? "已连接" : "已断开";
Map<String, Object> data = new HashMap<>();
data.put("enabled", mqttAutoOffManager.isEnabled());
data.put("mqtt", status);
data.put("clientId", safeClientId());
data.put("executorInited", mqttAutoOffManager.isExecutorInited());
data.put("totalTaskCount", mqttAutoOffManager.getTotalTaskCount());
data.put("deviceTaskCount", mqttAutoOffManager.getDeviceTaskCount());
return data;
}
// ======================== SmartLifecycle 生命周期管理(核心修复) ========================
/**
* MQTTSpring/
* @PostConstructMQTT
*/
@Override
public void start() {
log.info("开始监听");
if (isRunning.compareAndSet(false, true)) {
try {
// 初始化自动关任务线程池
mqttAutoOffManager.initExecutor(autoOffThreadPoolSize);
// 核心修改:无论是否已连接,都执行订阅
subscribeTopics();
log.info("【MQTT生命周期】客户端启动成功已设置回调+订阅主题),自动关闭任务线程池大小:{}", autoOffThreadPoolSize);
} catch (MqttException e) {
log.error("【MQTT生命周期】客户端启动失败", e);
isRunning.set(false);
}
}
}
/**
* MQTT
* 线
*/
@Override
public void stop() {
if (isRunning.compareAndSet(true, false)) {
try {
// 关闭自动关任务线程池
mqttAutoOffManager.shutdownExecutor();
// 关闭MQTT客户端
if (mqttClient != null) {
if (mqttClient.isConnected()) {
mqttClient.disconnect();
}
mqttClient.close();
log.info("【MQTT生命周期】客户端已优雅关闭");
}
} catch (Exception e) {
log.error("【MQTT生命周期】客户端关闭失败", e);
}
}
}
/**
*
*/
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
/**
* MQTT
*/
@Override
public boolean isRunning() {
return isRunning.get();
}
/**
* MQTTRedis
*/
@Override
public int getPhase() {
return 10;
}
/**
* trueSpringstart()
*/
@Override
public boolean isAutoStartup() {
return true;
}
}

View File

@ -0,0 +1,317 @@
package com.agri.framework.manager;
import com.agri.common.core.domain.entity.SysUser;
import com.agri.common.utils.SecurityUtils;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.service.ISysAgriInfoService;
import com.agri.system.service.ISysUserService;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* MQTT
*
* 1. -Redis
* 2. //
* 3. Redispipeline
* 4.
* JDK 8
*/
@Component
public class MqttSubscriptionManager {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(MqttSubscriptionManager.class);
/**
* Redis线
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ISysUserService sysUserService;
/**
*
*/
@Resource
private ISysAgriInfoService agriInfoService;
// 新增前端订阅关系TTL兜底“取消订阅失败/异常退出”——只维护subc:{clientId}的TTL
@Value("${spring.mqtt.subc-ttl-seconds:3600}")
private int subcTtlSeconds;
// 新增pipeline 批量 SISMEMBER subc:{clientId} deviceIdN次->1次往返 拿取失效的client
public List<Boolean> pipeIsMemberSubc(List<String> clientIds, String deviceId) {
if (clientIds == null || clientIds.isEmpty() || !StringUtils.hasText(deviceId)) {
return Collections.emptyList();
}
// ✅ 关键不发“占位命令”只对有效clientId发SISMEMBER同时保证返回结果与入参严格对齐
int n = clientIds.size();
// 创建长度n全部false的队列
List<Boolean> out = new ArrayList<>(Collections.nCopies(n, Boolean.FALSE));
List<Integer> idx = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
if (StringUtils.hasText(clientIds.get(i))) {
// 符合条件存进clientIds对应的索引
idx.add(i);
}
}
if (idx.isEmpty()) {
return out;
}
// 处理每个每个clientId的值是否还在 值存在rs中 执行 stringRedisTemplate.executePipelined
List<Object> rs = stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer();
byte[] member = serializer.serialize(deviceId);
for (int i : idx) {
String clientId = clientIds.get(i);
// 存放命令
connection.sIsMember(serializer.serialize("subc:" + clientId), member);
}
return null;
});
for (int j = 0; j < idx.size() && j < (rs == null ? 0 : rs.size()); j++) {
out.set(idx.get(j), Boolean.TRUE.equals(rs.get(j)));
}
return out;
}
// 对应的subc不存在 删除对应的sub pipeline 批量 SREM sub:{deviceId} clientId清理残留 N次->1次往返
public void pipeSRemSub(String deviceId, List<String> staleClientIds) {
if (!StringUtils.hasText(deviceId) || staleClientIds == null || staleClientIds.isEmpty()) {
return;
}
stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer();
byte[] subKey = serializer.serialize("sub:" + deviceId);
for (String clientId : staleClientIds) {
if (StringUtils.hasText(clientId)) {
connection.sRem(subKey, serializer.serialize(clientId));
}
}
return null;
});
}
/**
* Controller
*/
public void subscribeDevice(String clientId, String deviceId) {
if (!StringUtils.hasText(clientId) || !StringUtils.hasText(deviceId)) {
log.error("【订阅管理】clientId或deviceId不能为空");
throw new IllegalArgumentException("clientId和deviceId不能为空");
}
// 保存订阅关系到Redis
stringRedisTemplate.opsForSet().add("sub:" + deviceId, clientId);
stringRedisTemplate.opsForSet().add("subc:" + clientId, deviceId);
// 新增订阅成功后给subc设置TTL兜底“取消订阅失败/异常退出”)
stringRedisTemplate.expire("subc:" + clientId, subcTtlSeconds, TimeUnit.SECONDS);
log.info("【订阅管理】前端{}订阅设备{}成功", clientId, deviceId);
}
/**
* Controller
* clientId
*
* @param clientId
* @param deviceId ID
*/
public void unsubscribeDevice(String clientId, String deviceId) {
if (!StringUtils.hasText(clientId) || !StringUtils.hasText(deviceId)) {
log.error("【前端取消订阅】clientId或deviceId不能为空");
throw new IllegalArgumentException("clientId和deviceId不能为空");
}
// 从Redis删除订阅关系
stringRedisTemplate.opsForSet().remove("sub:" + deviceId, clientId);
stringRedisTemplate.opsForSet().remove("subc:" + clientId, deviceId);
log.info("【前端取消订阅】前端{}取消订阅设备{}成功", clientId, deviceId);
}
/**
* Controller
* @param clientId web_001app_002
* @return
*/
public int subscribeAllDeviceByUserId(String clientId) {
// 1. 入参校验
if (!StringUtils.hasText(clientId)) {
log.error("【全量订阅】clientId不能为空");
throw new IllegalArgumentException("clientId不能为空");
}
// 2. Redis连接可用性校验
try {
stringRedisTemplate.hasKey("test:connection");
} catch (Exception e) {
log.warn("【全量订阅】Redis连接不可用订阅操作跳过{}", e.getMessage());
return 0;
}
Long userId = SecurityUtils.getLoginUser().getUserId();
boolean update = sysUserService.lambdaUpdate()
.eq(SysUser::getUserId, userId)
.set(SysUser::getClientId, clientId)
.update();
if (update) {
log.info("【全量订阅】用户端mqtt客户端绑定成功");
}
// 3. 查询该用户名下的所有设备ID替换为你的实际设备查询逻辑
List<String> deviceIds = new ArrayList<>(agriInfoService.queryImeiByUserId(userId));
if (userId == 1) {
deviceIds.add("862538065276061");
}
if (deviceIds == null || deviceIds.isEmpty()) {
log.warn("【全量订阅】用户{}名下无可用设备", userId);
return 0;
}
// 过滤空设备ID避免无效操作
List<String> validDeviceIds = deviceIds.stream()
.filter(StringUtils::hasText)
.distinct()
.collect(Collectors.toList());
if (validDeviceIds.isEmpty()) {
log.warn("【全量订阅】用户{}名下无有效设备ID", userId);
return 0;
}
// 4. 批量写入Redis订阅关系兼容JDK 8的RedisCallback写法
try {
stringRedisTemplate.execute(new RedisCallback<Void>() {
@Override
public Void doInRedis(RedisConnection connection) throws DataAccessException {
// 获取String序列化器和stringRedisTemplate保持一致
RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer();
// 开启Redis事务
connection.multi();
byte[] clientIdBytes = serializer.serialize(clientId);
// 4.1 设备→前端给每个设备的订阅集合添加clientId
for (String deviceId : validDeviceIds) {
byte[] subKey = serializer.serialize("sub:" + deviceId);
connection.sAdd(subKey, clientIdBytes);
}
// 4.2 前端→设备给前端的订阅集合批量添加所有设备ID
byte[] subcKey = serializer.serialize("subc:" + clientId);
byte[][] deviceIdBytesArray = new byte[validDeviceIds.size()][];
for (int i = 0; i < validDeviceIds.size(); i++) {
deviceIdBytesArray[i] = serializer.serialize(validDeviceIds.get(i));
}
connection.sAdd(subcKey, deviceIdBytesArray);
// 新增给subc设置TTL兜底“取消订阅失败/异常退出”)
connection.expire(subcKey, subcTtlSeconds);
// 执行事务
connection.exec();
return null;
}
});
log.info("【全量订阅】前端{}成功订阅用户{}名下的{}个设备,设备列表:{}",
clientId, userId, validDeviceIds.size(), validDeviceIds);
return validDeviceIds.size();
} catch (Exception e) {
log.error("【全量------订阅】前端{}订阅用户{}名下设备失败", clientId, userId, e);
throw new RuntimeException("全量订阅失败:" + e.getMessage());
}
}
/**
*
* @param clientId
* @return MQTT
*/
public List<String> unsubscribeAllDevice(String clientId) {
// 1. 入参校验
if (!StringUtils.hasText(clientId)) {
log.error("【全量取消】clientId不能为空");
throw new IllegalArgumentException("clientId不能为空");
}
// 2. Redis连接可用性校验
try {
stringRedisTemplate.hasKey("test:connection");
} catch (Exception e) {
log.warn("【全量取消】Redis连接不可用取消操作跳过{}", e.getMessage());
return Collections.emptyList();
}
// 3. 查询该前端订阅的所有设备ID即用户名下所有设备
Set<String> deviceSet = stringRedisTemplate.opsForSet().members("subc:" + clientId);
if (deviceSet == null || deviceSet.isEmpty()) {
log.warn("【全量取消】前端{}无订阅的设备", clientId);
return Collections.emptyList();
}
// 4. 构建需要取消的MQTT主题列表
List<String> frontendTopics = new ArrayList<>();
for (String deviceId : deviceSet) {
frontendTopics.add("frontend/" + clientId + "/dtu/" + deviceId + "/listener");
}
// 5. 批量删除Redis订阅关系兼容JDK 8的RedisCallback写法
try {
stringRedisTemplate.execute(new RedisCallback<Void>() {
@Override
public Void doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer();
// 开启事务
connection.multi();
byte[] clientIdBytes = serializer.serialize(clientId);
// 5.1 批量删除设备→前端的订阅关系
for (String deviceId : deviceSet) {
byte[] subKey = serializer.serialize("sub:" + deviceId);
connection.sRem(subKey, clientIdBytes);
}
// 5.2 删除前端→设备的反向索引(核心:清空该前端的所有订阅设备)
byte[] subcKey = serializer.serialize("subc:" + clientId);
connection.del(subcKey);
// 执行事务
connection.exec();
return null;
}
});
} catch (Exception e) {
log.error("【全量取消】Redis批量删除失败", e);
throw new RuntimeException("全量取消订阅失败:" + e.getMessage());
}
log.info("【全量取消】前端{}成功取消{}个设备的订阅", clientId, deviceSet.size());
return frontendTopics;
}
}

View File

@ -0,0 +1,83 @@
package com.agri.framework.web.dispatcher;
import com.agri.common.utils.wechat.WxUtil;
import com.agri.framework.interceptor.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* MQTT
* Topic
* JDK 8
*/
@Component
public class MqttMessageDispatcher {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(MqttMessageDispatcher.class);
/**
*
*/
@Resource
private DeviceStatusHandler deviceStatusHandler;
/**
*
*/
@Resource
private FrontendControlHandler frontendControlHandler;
/**
* 线
*/
@Resource
private FrontendOnlineHandler frontendOnlineHandler;
@Autowired
private Environment env;
/**
* 线
*/
@Resource
private FrontendConfigHandler frontendConfigHandler;
/**
*
*
*
* @param topic
* @param payload JSON
*/
public void handleMessage(String topic, String payload) {
try {
// log.info("【MQTT接收】topic={}, payload={}", topic, payload);
if (env.acceptsProfiles("dev")) return;
// 设备状态主题dtu/{deviceId}/up
if (topic.matches("dtu/\\w+/\\w+")) {
deviceStatusHandler.handle(topic, payload);
}
// 处理前端控制指令主题frontend/{clientId}/control/{deviceId}
else if (topic.matches("frontend/\\w+/control/\\w+")) {
frontendControlHandler.handle(topic, payload);
}
// 新增前端在线心跳主题frontend/{clientId}/online
else if (topic.matches("frontend/\\w+/online")) {
frontendOnlineHandler.handle(topic, payload);
} // 新增前端在线心跳主题frontend/{clientId}/online
else if (topic.matches("frontend/\\w+/\\w+/config")) {
frontendConfigHandler.handle(topic, payload);
}
} catch (Exception e) {
WxUtil.pushText("【MQTT消息处理异常】\n topic: "+ topic+"\n cause: "+e);
log.error("【MQTT消息处理异常】topic={}", topic, e);
}
}
}

View File

@ -41,7 +41,7 @@ public class SysRegisterService
*/ */
public String register(RegisterBody registerBody) public String register(RegisterBody registerBody)
{ {
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(),phonenumber = registerBody.getPhonenumber();
SysUser sysUser = new SysUser(); SysUser sysUser = new SysUser();
sysUser.setUserName(username); sysUser.setUserName(username);
@ -56,6 +56,14 @@ public class SysRegisterService
{ {
msg = "用户名不能为空"; msg = "用户名不能为空";
} }
else if (StringUtils.isEmpty(phonenumber))
{
msg = "用户手机号不能为空";
}
else if (phonenumber.length() != 11)
{
msg = "手机号必须为11位";
}
else if (StringUtils.isEmpty(password)) else if (StringUtils.isEmpty(password))
{ {
msg = "用户密码不能为空"; msg = "用户密码不能为空";
@ -79,7 +87,17 @@ public class SysRegisterService
sysUser.setNickName(username); sysUser.setNickName(username);
sysUser.setPwdUpdateDate(DateUtils.getNowDate()); sysUser.setPwdUpdateDate(DateUtils.getNowDate());
sysUser.setPassword(SecurityUtils.encryptPassword(password)); sysUser.setPassword(SecurityUtils.encryptPassword(password));
// todo 默认用户部 考虑项目启动将所有部门和角色缓存到redis中修改时更新缓存
sysUser.setDeptId(203L);
sysUser.setPhonenumber(registerBody.getPhonenumber());
if (userService.checkPhoneUnique(sysUser))
{
msg = "当前手机号已被绑定!";
return msg;
}
boolean regFlag = userService.registerUser(sysUser); boolean regFlag = userService.registerUser(sysUser);
// 默认普通角色
userService.insertUserAuth(sysUser.getUserId(), new Long[] {2L});
if (!regFlag) if (!regFlag)
{ {
msg = "注册失败,请联系系统管理人员"; msg = "注册失败,请联系系统管理人员";

View File

@ -47,12 +47,6 @@
<version>3.5.3.1</version> <version>3.5.3.1</version>
</dependency> </dependency>
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version> <!-- 该版本完全兼容JDK 8 -->
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>

View File

@ -34,7 +34,15 @@
<groupId>com.agri</groupId> <groupId>com.agri</groupId>
<artifactId>agri-common</artifactId> <artifactId>agri-common</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.agri</groupId>
<artifactId>agri-system</artifactId>
</dependency>
<dependency>
<groupId>com.agri</groupId>
<artifactId>agri-framework</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,222 @@
package com.agri.quartz.task;
import com.agri.framework.interceptor.FrontendControlHandler;
import com.agri.framework.manager.AgriStatusManager;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysMessage;
import com.agri.system.service.AgriService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.agri.framework.config.MqttConfig;
import com.agri.system.service.ISysAgriInfoService;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 线 线
*/
@Component
public class AgriStatusTask {
private static final Logger log = LoggerFactory.getLogger(AgriStatusTask.class);
// Redis 前缀常量
private static final String SUB_KEY_PREFIX = "sub:";
private static final String LOCK_KEY = "lock:device:online:push";
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${spring.mqtt.dtu-ctl-lock-ttl:60}")
private int lockTtl;
@Autowired
private ISysAgriInfoService agriInfoService;
@Autowired
private AgriStatusManager agriStatusManager;
@Autowired
private AgriService agriService;
@Resource
private FrontendControlHandler frontendControlHandler;
/**
* 10
* 1. sub: key IMEI
* 2. 线
* 3. 线 MQTT frontend/{imei}/online
*/
public void pushDeviceStatus() {
// 1. 加分布式锁,避免集群重复执行
Boolean lockSuccess = stringRedisTemplate.opsForValue()
.setIfAbsent(LOCK_KEY, "running", lockTtl, TimeUnit.SECONDS);
// 补充处理Redis连接异常的情况
if (lockSuccess == null) {
log.error("获取分布式锁失败Redis连接异常");
return;
}
if (!lockSuccess) {
log.debug("其他节点正在执行,跳过本次推送");
return;
}
long startTime = System.currentTimeMillis();
try {
// 查询大棚列表所有在线设备
List<SysAgriInfo> agriInfos = agriInfoService.findAgriByUser(new SysAgriInfo());
List<String> imeiList = agriService.queryAllGreenhouseImei(agriInfos);
if (imeiList.isEmpty()) {
log.info("大棚表无数据,结束推送");
return;
}
Map<String, Map<String, Boolean>> statusMap
= agriStatusManager.batchCheckDeviceOnline(imeiList);
// 3. 首页状态
agriStatusManager.asyncBatchPushMqtt(statusMap);
// 4. 离线设备
List<SysAgriInfo> offlineDevices = findOfflineDevices(agriInfos, statusMap);
if (offlineDevices.isEmpty()) return;
// 5. 保存消息中心
List<SysMessage> messages = agriService.saveMessage(offlineDevices);
// 6. 发送告警消息
frontendControlHandler.sendAlarmMessage(messages);
} catch (Exception e) {
log.error("设备在线状态推送任务异常", e);
// 可选:异常告警(如企业微信/钉钉)
// WxUtil.pushText("【设备在线状态推送异常】\n" + e.getMessage());
} finally {
// 释放锁可选也可依赖TTL自动过期
stringRedisTemplate.delete(LOCK_KEY);
}
}
// 查找离线设备
private List<SysAgriInfo> findOfflineDevices(List<SysAgriInfo> agriInfos,
Map<String, Map<String, Boolean>> statusMap) {
if (statusMap.isEmpty()) {
log.info("不存在任何imei");
return new ArrayList<>();
}
Map<String, Object> offlineMap = new HashMap<>();
for (Map.Entry<String, Map<String, Boolean>> map : statusMap.entrySet()) {
if (!map.getValue().get("imeiOnline")) {
offlineMap.put(map.getKey(), map.getValue());
}
}
if (offlineMap.isEmpty()) return new ArrayList<>();
List<SysAgriInfo> offlineDevices = new ArrayList<>();
for (SysAgriInfo agriInfo : agriInfos) {
if (offlineMap.containsKey(agriInfo.getImei())) {
agriInfo.setTitle("设备离线告警");
agriInfo.setMsg("怀疑设备离线!请及时检查");
offlineDevices.add(agriInfo);
log.info("设备{} 不存在设备状态", agriInfo.getImei());
}
}
return offlineDevices;
}
/*
* LuaIMEIsub:{imei}
* 1Redis +
* @return IMEI线key=IMEIvalue=线
*/
public Map<String, Boolean> getGreenhouseOnlineStatusByLua() {
// 1. 从大棚表获取所有合法IMEI
List<String> allGreenhouseImeiList = agriInfoService.queryImeiByUserId(null);
if (allGreenhouseImeiList.isEmpty()) {
log.info("大棚表无合法IMEI返回空");
return new HashMap<>();
}
// 2. 简化版Lua脚本批量查sub:{imei}是否存在)
String luaScript = "" +
"local imeiList = ARGV\n" +
"local prefix = KEYS[1]\n" +
"local result = {}\n" +
"for _, imei in ipairs(imeiList) do\n" +
" local key = prefix .. imei\n" +
" local isOnline = redis.call('EXISTS', key)\n" +
" table.insert(result, imei)\n" +
" table.insert(result, tostring(isOnline))\n" +
"end\n" +
"return result";
// ===== 核心修改保留你指定的写法仅做JDK 8兼容 =====
DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript);
redisScript.setResultType(List.class); // 保留原有写法
// 3. 构造参数
List<String> keys = Collections.singletonList(SUB_KEY_PREFIX);
List<String> args = allGreenhouseImeiList;
try {
// 4. 执行脚本JDK 8 强制类型转换,安全兼容)
List resultList = stringRedisTemplate.execute(redisScript, keys, args.toArray(new String[0]));
// 5. 解析结果兼容JDK 8 原始List类型
Map<String, Boolean> onlineStatusMap = new HashMap<>();
if (resultList != null && resultList.size() >= 2) {
for (int i = 0; i < resultList.size(); i += 2) {
// JDK 8 显式转换为String避免类型异常
String imei = String.valueOf(resultList.get(i));
String isOnlineStr = String.valueOf(resultList.get(i + 1));
onlineStatusMap.put(imei, "1".equals(isOnlineStr));
}
}
return onlineStatusMap;
} catch (Exception e) {
log.error("Lua脚本执行失败", e);
return new HashMap<>();
}
}
// ========== 方式1用 ScanCursor 遍历 sub:* Key ==========
private List<String> scanAllSubDeviceImei() {
List<String> imeiList = new ArrayList<>();
// count 建议一次扫描1000个槽位
ScanOptions scanOptions = ScanOptions.scanOptions()
.match(SUB_KEY_PREFIX + "*")
.count(1000)
.build();
// JDK 8 需显式声明 Cursor<byte[]> 泛型try-with-resources 自动关闭游标
try (Cursor<byte[]> cursor = stringRedisTemplate.getConnectionFactory()
.getConnection()
.scan(scanOptions)) {
while (cursor.hasNext()) {
byte[] keyBytes = cursor.next();
String key = new String(keyBytes, StandardCharsets.UTF_8);
if (key.startsWith(SUB_KEY_PREFIX)) {
String imei = key.substring(SUB_KEY_PREFIX.length());
imeiList.add(imei);
}
}
} catch (Exception e) {
log.error("Cursor 扫描 sub: 前缀 key 失败", e);
}
return imeiList;
}
}

View File

@ -0,0 +1,76 @@
package com.agri.quartz.task;
import com.agri.common.utils.spring.SpringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@Component("agriTask")
public class AgriTask {
private static final Logger log = LoggerFactory.getLogger(AgriTask.class);
public void clearInvalidCache() {
log.info("=============【定时任务】开始执行Redis sub: 键清理任务=============");
int deletedCount = 0;
try {
// 重点*****
RedisTemplate<String, Object> redisTemplate = SpringUtils.getBean("redisTemplate");
if (redisTemplate == null) {
log.error("RedisTemplate 获取失败,清理任务终止");
return;
}
ScanOptions scanOptions = ScanOptions.scanOptions()
.match("sub:*")
.count(1000)
.build();
Cursor<byte[]> cursor = redisTemplate.executeWithStickyConnection(connection ->
connection.scan(scanOptions)
);
List<String> batchKeys = new ArrayList<>(1000);
while (cursor != null && cursor.hasNext()) {
byte[] keyBytes = cursor.next();
String key = new String(keyBytes, StandardCharsets.UTF_8);
batchKeys.add(key);
if (batchKeys.size() >= 1000) {
deletedCount += batchKeys.size();
redisTemplate.delete(batchKeys);
log.info("批量删除 {} 个sub: 键", batchKeys.size());
batchKeys.clear();
}
}
if (!batchKeys.isEmpty()) {
deletedCount += batchKeys.size();
redisTemplate.delete(batchKeys);
log.info("批量删除剩余 {} 个sub: 键", batchKeys.size());
}
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
log.error("关闭 Redis 游标失败", e);
}
}
log.info("=============【定时任务】Redis sub: 键清理完成,总计删除 {} 个键=============", deletedCount);
} catch (Exception e) {
log.error("=============【定时任务】Redis sub: 键清理失败=============", e);
}
}
}

View File

@ -0,0 +1,127 @@
package com.agri.quartz.task;
import com.agri.framework.interceptor.FrontendControlHandler;
import com.agri.system.domain.SysMessage;
import com.agri.system.service.AgriService;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.service.ISysAgriInfoService;
import com.agri.system.service.ISysDtuDataService;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Component
public class AgriTempTask {
private static final Logger log = LoggerFactory.getLogger(AgriTempTask.class);
@Autowired
private ISysDtuDataService dtuDataService;
@Autowired
private ISysAgriInfoService agriInfoService;
private static final String LOCK_KEY = "lock:check:temp:push";
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${spring.mqtt.dtu-ctl-lock-ttl:60}")
private int lockTtl;
@Autowired
private AgriService agriService;
@Resource
private FrontendControlHandler frontendControlHandler;
public void checkTempStatus() {
if (!acquireLock()) {
return;
}
try {
List<SysAgriInfo> agriInfos = agriInfoService.findAgriByUser(new SysAgriInfo());
List<String> imeiList = agriService.queryAllGreenhouseImei(agriInfos);
if (CollectionUtils.isEmpty(imeiList)) {
log.info("大棚表无数据,结束推送");
return;
}
Map<String, Object> latestDataMap = queryLatestDtuData(imeiList);
List<SysAgriInfo> offlineDevices = findOfflineDevices(agriInfos, latestDataMap);
if (CollectionUtils.isEmpty(offlineDevices)) return;
List<SysMessage> messages = agriService.saveMessage(offlineDevices);
// 推送离线告警
frontendControlHandler.sendAlarmMessage(messages);
} catch (Exception e) {
log.error("设备在线状态推送任务异常", e);
} finally {
releaseLock();
}
}
// 获取分布式锁
private boolean acquireLock() {
Boolean lockSuccess = stringRedisTemplate.opsForValue()
.setIfAbsent(LOCK_KEY, "agriTempTask", lockTtl, TimeUnit.SECONDS);
if (lockSuccess == null) {
log.error("获取分布式锁失败Redis连接异常");
return false;
}
if (!lockSuccess) {
log.debug("其他节点正在执行,跳过本次推送");
return false;
}
return true;
}
// 释放分布式锁
private void releaseLock() {
stringRedisTemplate.delete(LOCK_KEY);
}
// 查询所有大棚的最新温湿度数据
private Map<String, Object> queryLatestDtuData(List<String> imeiList) {
List<Map<String, Object>> dtuDataList = dtuDataService.getLastDtuDataByImeiList(imeiList);
Map<String, Object> dataMap = new HashMap<>();
if (CollectionUtils.isEmpty(dtuDataList)) {
return dataMap;
}
for (Map<String, Object> item : dtuDataList) {
Object imei = item.get("imei");
if (imei != null) {
dataMap.put(imei.toString(), item);
}
}
return dataMap;
}
// 查找离线设备
private List<SysAgriInfo> findOfflineDevices(List<SysAgriInfo> agriInfos,
Map<String, Object> latestDataMap) {
if (CollectionUtils.isEmpty(agriInfos)) {
return agriInfos;
}
List<SysAgriInfo> offlineList = new ArrayList<>();
for (SysAgriInfo agriInfo : agriInfos) {
if (!latestDataMap.containsKey(agriInfo.getImei())) {
agriInfo.setTitle("温度离线");
agriInfo.setMsg("怀疑温度离线!请检查");
offlineList.add(agriInfo);
log.info("设备{} 不存在温湿度数据", agriInfo.getImei());
}
}
return offlineList;
}
}

View File

@ -0,0 +1,467 @@
package com.agri.quartz.task;
import cn.hutool.core.map.MapUtil;
import com.agri.common.enums.TempCommandStatus;
import com.agri.common.utils.*;
import com.agri.common.utils.wechat.WxUtil;
import com.agri.framework.config.MqttConfig;
import com.agri.framework.manager.MqttAutoOffManager;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysAgriLimit;
import com.agri.system.domain.SysDevOperLog;
import com.agri.system.domain.SysImeiAutoLog;
import com.agri.system.domain.vo.RollerTermVO;
import com.agri.system.service.*;
import org.apache.commons.collections4.CollectionUtils;
import org.checkerframework.checker.units.qual.A;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
*
* 1imei
* 2imeidtu_data
* 3 +
* 4
* todo
*/
@Component("rollerAutoTask")
public class RollerAutoTask {
/**
* 使SLF4JJDK 8
*/
private static final Logger log = LoggerFactory.getLogger(RollerAutoTask.class);
@Autowired
private ISysAgriInfoService agriInfoService;
@Autowired
private ISysDtuDataService dtuDataService;
@Autowired
private ISysRollerParamService rollerParamService;
@Resource
private MqttConfig.MqttMessageSender mqttMessageSender;
@Autowired
private ISysDevOperLogService devOperLogService;
@Autowired
private MqttAutoOffManager autoOffManager;
/**
* Redis线
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ISysAgriLimitService agriLimitService;
@Value("${spring.mqtt.dtu-ctl-lock-ttl}")
private int dtuCtlLockTTL;
@Autowired
private ISysImeiAutoLogService imeiAutoLogService;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final Map<String, Function<SysAgriLimit, Integer>> LIMIT_MAP = new HashMap<>();
private static final Set<String> VALID_FUNC_CODES = new HashSet<>();
static {
LIMIT_MAP.put("jm1g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm1gLimit())));
LIMIT_MAP.put("jm2g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm2gLimit())));
LIMIT_MAP.put("jbg1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJbgLimit())));
LIMIT_MAP.put("jm3g1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm3gLimit())));
LIMIT_MAP.put("jm2k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm2kLimit())));
LIMIT_MAP.put("jm3k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm3kLimit())));
LIMIT_MAP.put("jbk1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJbkLimit())));
LIMIT_MAP.put("jm1k1", agriLimit -> Integer.parseInt(String.valueOf(agriLimit.getJm1kLimit())));
}
// ========== 常量定义(新增) ==========
private static final int WORK_MODE_AUTO = 1; // 自动模式
private static final int NOT_DELETED = 0; // 未删除
private static final String LOCK_PREFIX = "lock:";
private static final String AUTO_MODE = "auto_mode";
private static final String CREATE_BY = "条件控制";
private static final int OP_SOURCE = 3; // 操作来源:条件控制
private static final int LOCK_ACQUIRED = 1; // 是否获取锁
private static final int DEFAULT_RUN_TIME = 300; // 默认运行时间
public void checkAutoTerm() {
try {
log.info("=============【定时任务】大棚自动模式监测开始执行,时间:{}=============", LocalDateTime.now());
// 查询自动模式的大棚
List<SysAgriInfo> agriInfos = agriInfoService.lambdaQuery()
.select(SysAgriInfo::getImei, SysAgriInfo::getAgriName)
.eq(SysAgriInfo::getWorkMode, WORK_MODE_AUTO)
.eq(SysAgriInfo::getIsDeleted, NOT_DELETED)
.list();
if (CollectionUtils.isEmpty(agriInfos)) return;
// 取imei集合
List<String> imeiList = agriInfos.stream().map(SysAgriInfo::getImei).collect(Collectors.toList());
if (CollectionUtils.isEmpty(imeiList)) return;
// 根据imei 查询dtu_data最后一条温度数据
List<Map<String, Object>> dtuDataList = dtuDataService.getLastDtuDataByImeiList(imeiList);
if (CollectionUtils.isEmpty(dtuDataList)) return;
// 根据温湿度imei分组
Map<String, List<Map<String, Object>>> dtuDataByImeiMap
= dtuDataList.stream().collect(Collectors.groupingBy(map -> (String) map.get("imei")));
// 获取所有开启自动化模式的大棚列表的卷膜参数和条件设置
List<RollerTermVO> rollerTermList = rollerParamService.getRollerTerms(imeiList);
if (CollectionUtils.isEmpty(rollerTermList)) {
// todo 无参数设置和条件列表直接返回
log.error("【定时任务-卷膜自动化控制】无参数设置和条件列表直接返回!");
return;
}
// 按imei分组 → 再按roller分组一步到位
Map<String, Map<String, List<RollerTermVO>>> rollerTermMap = rollerTermList.stream()
.collect(Collectors.groupingBy(
RollerTermVO::getImei,
Collectors.groupingBy(RollerTermVO::getRoller)
));
// 查询每个IMEI今天的第一条日志
Map<String, Map<String, Integer>> todayLogCountByImeiMap = devOperLogService.getTodayLogCountByImeiMap(imeiList);
ArrayList<SysImeiAutoLog> logArrayList = new ArrayList<SysImeiAutoLog>();
// 循环所有开启自动化的大棚
for (SysAgriInfo agriInfo : agriInfos) {
String imei = agriInfo.getImei();
String agriName = agriInfo.getAgriName();
// 获取温湿度指定imei的数据
List<Map<String, Object>> dtuDataInfo = dtuDataByImeiMap.get(imei);
// 该大棚温湿度不存在
if (CollectionUtils.isEmpty(dtuDataInfo)) {
// todo 该大棚下1分钟内无最新温湿度怀疑离线
log.error("【定时任务-卷膜自动化控制】大棚『{}』1分钟内无最新温湿度怀疑离线", imei);
continue;
}
// 最后一条对应imei对应温度 及时间
Map<String, Object> dtuData = dtuDataInfo.get(0);
// 求温度上报时间
LocalDateTime dtuTime = (LocalDateTime) dtuData.get("time");
if (dtuTime == null) {
// todo 当前大棚温湿度时间为空 跳过
log.error("【定时任务-卷膜自动化控制】大棚『{}』温湿度时间「{}」为空, 跳过", imei, LocalDateTime.now().minusMinutes(1));
continue;
}
// 获取当前imei下的所有参数设置以及卷膜自动化条件设置
Map<String, List<RollerTermVO>> configTermByRollerMap = rollerTermMap.get(imei);
if (MapUtil.isEmpty(configTermByRollerMap)) {
// todo 当前大棚下没有设置条件或者参数
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前大棚下没有设置条件或者参数, 跳过", imei);
continue;
}
// 获取今天对应imei的日志
Map<String, Integer> todayLogByRoller = todayLogCountByImeiMap.get(imei);
for (Map.Entry<String, List<RollerTermVO>> configEntry : configTermByRollerMap.entrySet()) {
String roller = configEntry.getKey(); // 当前卷膜
List<RollerTermVO> terms = configEntry.getValue(); // 当前卷膜条件
// 每个卷膜分组只会有一个卷膜参数设置,所有取第一个即可
if (terms == null || terms.isEmpty()) {
// todo 当前卷膜 无参数设置跳过当前roller
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」无参数设置跳过当前roller", imei, roller);
continue;
}
LocalDateTime startTime = terms.stream()
.max(Comparator.comparing(RollerTermVO::getStartTime))
.get()
.getStartTime();
// 获取卷膜参数
RollerTermVO rollerConfig = terms.get(0);
String refTempCode = rollerConfig.getRefTempCode(); // 参考温度
BigDecimal ventTotalLen = rollerConfig.getVentTotalLen(); // 风口总长
BigDecimal reservedLen = rollerConfig.getReservedLen(); // 预留风口
// 防御性判断避免dtuData.get(refTempCode)为null
Object tempObj = dtuData.get(refTempCode);
if (tempObj == null) {
// todo 当前卷膜参考温度设置为空
log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」参考温度设置为空,跳过!", imei, roller);
continue;
}
// 优化明确标注为当前roller的参考温度快照仅解析一次
BigDecimal currentTemp = new BigDecimal(tempObj.toString());
// 遍历当前roller的所有term改用普通for循环可读性更高
for (RollerTermVO term : terms) {
// 判断该温度上报时间是否在该条件设置的时间范围内
boolean inRange = TimeRangeUtil.isTimeInRange(dtuTime, term.getStartTime(), term.getEndTime());
boolean isCancelOff = startTime.equals(term.getStartTime());
log.info("\n【定时任务-卷膜自动化控制】正在监测大棚『{}』-卷膜『{}』:" +
"条件设置详情,监控时间范围:『{}~{}』,适宜温度:{}℃,运行风口:{}cm。「{}」",
imei,roller,term.getStartTime().format(FORMATTER),term.getEndTime().format(FORMATTER),
term.getTemp(),term.getVent(), isCancelOff?"当前为卷膜「"+roller+"」最后一条自动化规则":"");
if (!inRange) {
log.info("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」温湿度时间:「{}」不在监控时间范围内「{}~{}」,跳过!",
imei, roller, dtuTime,term.getStartTime().format(FORMATTER),term.getEndTime().format(FORMATTER));
continue;
}
// 在范围内
//判断温度是否在适宜温度内
TempCommandStatus tempCommandStatus = TempJudgeUtil.judgeTempCommand(currentTemp, term.getTemp());
SysImeiAutoLog imeiAutoLog = new SysImeiAutoLog();
imeiAutoLog.setImei(imei);
imeiAutoLog.setMonitorPeriod(term.getStartTime().format(FORMATTER)+"~"+term.getEndTime().format(FORMATTER));
imeiAutoLog.setCurrentTemp(currentTemp);
imeiAutoLog.setRefTemp(refTempCode);
imeiAutoLog.setSuitableTemp(term.getTemp());
imeiAutoLog.setFanStatus(term.getVent());
imeiAutoLog.setIsLast(isCancelOff?1:0);
// todo 开关指令需要通知用户 推送主题 && 更新数据 前端重新请求消息表
if (tempCommandStatus == TempCommandStatus.OPEN) {
log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,触发自动化条件,即将执行『开』指令!",
imei, roller, currentTemp, term.getTemp());
// 判断是否首次开
Integer openLen = todayLogByRoller.getOrDefault(roller + "k1", 0);
imeiAutoLog.setRollFilm(roller);
imeiAutoLog.setExecCmd("开");
// 开指令
sendOpenCommand(imei, agriName, roller, openLen == 0, term.getVent(), reservedLen);
// 每次后,数量累计+1 不需要因为只循环到一次
} else if (tempCommandStatus == TempCommandStatus.CLOSE) {
log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,触发自动化条件,即将执行『关』指令!",
imei, roller, currentTemp, term.getTemp());
// 判断是否首次开
Integer closeLen = todayLogByRoller.getOrDefault(roller + "g1", 0);
imeiAutoLog.setRollFilm(roller);
imeiAutoLog.setExecCmd("关");
// 关指令
sendCloseCommand(imei, agriName, roller, closeLen == 0, term.getVent(), isCancelOff);
// 每次后,数量累计+1
} else {
log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,温度适宜,无需操作!",
imei, roller, currentTemp, term.getTemp());
}
logArrayList.add(imeiAutoLog);
}
}
}
imeiAutoLogService.saveBatch(logArrayList);
log.info("=============【定时任务】大棚自动模式监测执行完毕,时间:{}=============", LocalDateTime.now());
} catch (Exception e) {
WxUtil.pushText("【定时任务】大棚自动模式监测执行终端, \n发生错误.错误原因:"+e+"\n时间"+LocalDateTime.now());
log.error("\n=============【定时任务】大棚自动模式监测执行终端,\n发生错误.错误原因:{}\n时间{}=============", e, LocalDateTime.now());
}
}
/**
*
* @param imei
* @param agriName
* @param roller
* @param isFirstRun
* @param vent
* @param reservedLen
*/
private void sendOpenCommand(String imei, String agriName, String roller, boolean isFirstRun,
BigDecimal vent, BigDecimal reservedLen) {
// ========== 1. 前置参数校验(防御性判断) ==========
validateBaseParams(imei, agriName, roller);
if (vent == null) {
log.error("【开指令】设备{}卷膜{}风口大小为空,指令发送失败", imei, roller);
return;
}
if (isFirstRun && reservedLen == null) {
log.error("【开指令】设备{}卷膜{}首次执行但预留风口为空,指令发送失败", imei, roller);
return;
}
// ========== 2. 计算开指令风口长度 ==========
BigDecimal openLen = vent;
if (isFirstRun) {
log.error("【自动模式】设备【{}】卷膜【{}】今日首次执行开指令,叠加预留风口{}", imei, roller, reservedLen);
openLen = openLen.add(reservedLen);
}
String funcType = roller + "k1";
String message = buildMqttMessage(funcType);
// ========== 3. 执行核心指令逻辑 ==========
executeCommand(imei, agriName, roller, funcType, message, openLen, true, false);
}
/**
*
* @param imei
* @param agriName
* @param roller
* @param isFirstRun
* @param vent
*/
private void sendCloseCommand(String imei, String agriName, String roller,
boolean isFirstRun, BigDecimal vent, boolean isCancelOff) {
// ========== 1. 前置参数校验 ==========
validateBaseParams(imei, agriName, roller);
if (isFirstRun) {
log.error("【关指令】设备{}卷膜{}今日首次执行,直接返回不发送关指令", imei, roller);
return;
}
if (vent == null) {
log.error("【关指令】设备{}卷膜{}风口大小为空,指令发送失败", imei, roller);
return;
}
// ========== 2. 构建关指令参数 ==========
String funcType = roller + "g1";
String message = buildMqttMessage(funcType);
// ========== 3. 执行核心指令逻辑 ==========
executeCommand(imei, agriName, roller, funcType, message, vent, false, isCancelOff);
}
// ========== 抽取公共方法:减少代码冗余 ==========
/**
*
* @param imei ID
* @param agriName
* @param roller
*/
private void validateBaseParams(String imei, String agriName, String roller) {
if (StringUtils.isBlank(imei)) {
throw new IllegalArgumentException("设备IMEI不能为空");
}
if (StringUtils.isBlank(agriName)) {
log.warn("【指令】设备{}卷膜{}大棚名称为空,仍继续执行指令", imei, roller);
}
if (StringUtils.isBlank(roller)) {
throw new IllegalArgumentException("卷膜标识不能为空");
}
}
/**
* MQTT
* @param funcType roller1k1/roller1g1
* @return MQTT
*/
private String buildMqttMessage(String funcType) {
return String.format("{\"%s\":1}", funcType);
}
/**
* ++MQTT+
* @param imei ID
* @param agriName
* @param roller
* @param funcType
* @param message MQTT
* @param len
* @param isOpen
*/
private void executeCommand(String imei, String agriName, String roller, String funcType,
String message, BigDecimal len, boolean isOpen, boolean isCancelOff) {
String lockKey = LOCK_PREFIX + imei + ":" + funcType;
try {
// ========== 1. 获取分布式锁 ==========
Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent(
lockKey, AUTO_MODE, dtuCtlLockTTL, TimeUnit.SECONDS
);
if (lockSuccess == null || !lockSuccess) {
log.warn("【分布式锁】自动模式下{}设备{}的{}功能失败,可能存在并发操作",
isOpen ? "开启" : "关闭", imei, funcType);
return;
}
// ========== 2. 记录操作日志 ==========
log.debug("【指令处理】自动模式下触发{}设备{}的{}功能,指令:{}",
isOpen ? "开启" : "关闭", imei, funcType, message);
// ========== 3. 发布MQTT指令 ==========todo
mqttMessageSender.publish("dtu/" + imei + "/down", message);
// ========== 4. 计算运行时间并调度自动关 ==========
int runTime = RollerTimeCalculator.calculateRunTime(len);
if (isCancelOff) {
// 如果最后一条则遵循手动限位设置如果没设置默认300秒
SysAgriLimit agriLimit = agriLimitService.lambdaQuery()
.eq(SysAgriLimit::getImei, imei)
.one();
if (agriLimit != null) {
runTime = LIMIT_MAP.getOrDefault(funcType, k -> DEFAULT_RUN_TIME).apply(agriLimit);
}
log.debug("【自动关调度】设备{}卷膜{}:触发最后一条自动化条件,遵循手动限位设置!时间:{}", imei, roller, LocalDateTime.now());
}
if (runTime > 0) {
String autoOffKey = roller + (isOpen ? "k1" : "g1");
autoOffManager.scheduleAutoOff(imei, autoOffKey, runTime);
log.debug("【自动关调度】设备{}卷膜「{}:{}」调度{}秒后自动关闭", imei, roller,(isOpen?"开":"关"), runTime);
}
saveOperLog(imei, agriName, funcType, message, isOpen ? 1 : 0, isCancelOff, runTime);
}
// catch (MqttException e) {
// // ========== 异常处理:记录详细日志,不抛运行时异常 ==========
// log.error("【MQTT异常】{}设备{}的{}功能指令发布失败,指令:{}",
// isOpen ? "开启" : "关闭", imei, funcType, message, e);
// }
catch (Exception e) {
// 兜底异常捕获避免未知异常导致锁无法释放依赖TTL兜底
log.error("【指令执行异常】{}设备{}的{}功能执行失败",
isOpen ? "开启" : "关闭", imei, funcType, e);
}
}
/**
*
* @param imei ID
* @param agriName
* @param funcCode
* @param payload
* @param opType 1-0-
*/
private void saveOperLog(String imei, String agriName,
String funcCode, String payload, int opType, boolean isCancelOff, int runTime) {
SysDevOperLog logDto = new SysDevOperLog();
logDto.setAgriName(agriName);
logDto.setImei(imei);
logDto.setFuncCode(funcCode);
logDto.setOpType(opType);
logDto.setOpSource(OP_SOURCE);
logDto.setPayload(payload);
logDto.setRunTime(runTime);
logDto.setLockAcquired(LOCK_ACQUIRED); // 已获取锁
logDto.setLockHolder(AUTO_MODE);
logDto.setIsTask(isCancelOff?0:1);
if (isCancelOff) {
logDto.setNoTaskReason("触发最后一条自动化指令遵循手动限位设置默认300秒");
}
logDto.setCreateBy(CREATE_BY);
// 可选:增加异常捕获,避免日志保存失败影响指令执行
try {
devOperLogService.save(logDto);
log.info("【日志保存】设备{}功能{}日志保存成功,日志id:{}", imei, funcCode,logDto.getId());
} catch (Exception e) {
log.error("【日志保存失败】设备{}功能{}日志保存失败", imei, funcCode, e);
}
}
}

View File

@ -0,0 +1,28 @@
package com.agri.quartz.task;
import com.agri.system.domain.SysDtuData;
import com.agri.system.service.ISysDtuDataService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component("tempTask")
public class TempTask {
@Autowired
private ISysDtuDataService dtuDataService;
public void ryNoParams()
{
dtuDataService.lambdaUpdate()
.eq(SysDtuData::getImei, "864865085008523")
.orderByDesc(SysDtuData::getId)
.last("limit 1")
.set(SysDtuData::getTime, new Date())
.set(SysDtuData::getCreateTime, new Date())
.update();
}
}

View File

@ -5,22 +5,21 @@ import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult; import com.agri.common.core.domain.AjaxResult;
import com.agri.common.core.page.TableDataInfo; import com.agri.common.core.page.TableDataInfo;
import com.agri.common.enums.BusinessType; import com.agri.common.enums.BusinessType;
import com.agri.common.utils.SecurityUtils;
import com.agri.common.utils.poi.ExcelUtil; import com.agri.common.utils.poi.ExcelUtil;
import com.agri.system.domain.SysAgriInfo; import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysUserAgri;
import com.agri.system.domain.vo.AgriInfoView;
import com.agri.system.service.ISysAgriInfoService; import com.agri.system.service.ISysAgriInfoService;
import com.agri.system.service.ISysUserAgriService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Controller * Controller
@ -35,6 +34,8 @@ public class SysAgriInfoController extends BaseController
@Autowired @Autowired
private ISysAgriInfoService sysAgriInfoService; private ISysAgriInfoService sysAgriInfoService;
@Autowired
private ISysUserAgriService userAgriService;
/** /**
* *
*/ */
@ -43,10 +44,16 @@ public class SysAgriInfoController extends BaseController
public TableDataInfo list(SysAgriInfo sysAgriInfo) public TableDataInfo list(SysAgriInfo sysAgriInfo)
{ {
startPage(); startPage();
List<SysAgriInfo> list = sysAgriInfoService.selectSysAgriInfoList(sysAgriInfo); List<SysAgriInfo> list = sysAgriInfoService.findAgriByUser(sysAgriInfo);
return getDataTable(list); return getDataTable(list);
} }
@PreAuthorize("@ss.hasPermi('assets:agri:list')")
@GetMapping("/findAgriByUser")
public AjaxResult findAgriByUser(SysAgriInfo sysAgriInfo) {
List<SysAgriInfo> list = sysAgriInfoService.findAgriByUser(sysAgriInfo);
return success(list);
}
/** /**
* *
*/ */
@ -102,4 +109,74 @@ public class SysAgriInfoController extends BaseController
{ {
return toAjax(sysAgriInfoService.deleteSysAgriInfoByIds(ids)); return toAjax(sysAgriInfoService.deleteSysAgriInfoByIds(ids));
} }
@PreAuthorize("@ss.hasPermi('assets:agri:list')")
@GetMapping("/findAgriInfoByUser")
public AjaxResult findAgriInfoByUser(SysAgriInfo sysAgriInfo) {
List<AgriInfoView> list = sysAgriInfoService.findAgriInfoByUser(sysAgriInfo);
return success(list);
}
/**
*
* @param sysAgriInfo
* @return
*/
@PreAuthorize("@ss.hasPermi('assets:agri:addAgriFromMobile')")
@PostMapping("/addAgriFromMobile")
public AjaxResult addAgriFromMobile(@RequestBody SysAgriInfo sysAgriInfo) {
return success(sysAgriInfoService.addAgriFromMobile(sysAgriInfo));
}
@PreAuthorize("@ss.hasPermi('assets:agri:switchAgriMode')")
@PostMapping("/switchAgriMode/{imei}")
public AjaxResult switchAgriMode(@PathVariable String imei, @RequestParam("code") Integer code) {
if (StringUtils.isEmpty(imei) || code == null) {
return error();
}
sysAgriInfoService.lambdaUpdate()
.eq(SysAgriInfo::getImei, imei)
.set(SysAgriInfo::getWorkMode, code)
.update();
return success();
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:agri:edit')")
@Log(title = "大棚管理", businessType = BusinessType.UPDATE)
@PutMapping("/renameAgriName")
public AjaxResult renameAgriName(@RequestParam("agriId") String agriId,
@RequestParam("imei") String imei,
@RequestParam("newAgriName") String newAgriName)
{
boolean update = false;
if (SecurityUtils.isAdmin()) {
update = sysAgriInfoService.lambdaUpdate()
.eq(SysAgriInfo::getImei, imei)
.set(SysAgriInfo::getAgriName, newAgriName)
.update();
} else {
update = userAgriService.lambdaUpdate()
.eq(SysUserAgri::getUserId, SecurityUtils.getUserId())
.eq(SysUserAgri::getAgriId, imei)
.set(SysUserAgri::getAgriName, newAgriName)
.update();
}
return update? success():error();
}
/**
* +
*/
@PreAuthorize("@ss.hasPermi('assets:agri:list')")
@GetMapping("/selectShareInfo")
public AjaxResult selectShareInfo(SysAgriInfo agriInfo) {
Map<String, Object> result = sysAgriInfoService.selectShareInfoByUser(agriInfo);
return success(result);
}
} }

View File

@ -0,0 +1,124 @@
package com.agri.system.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.agri.system.domain.SysAgriLimit;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType;
import com.agri.system.domain.SysAgriLimitBak;
import com.agri.system.service.ISysAgriLimitBakService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
/**
* Controller
*
* @author lld
* @date 2026-03-17
*/
@RestController
@RequestMapping("/assets/limit_bak")
public class SysAgriLimitBakController extends BaseController
{
@Autowired
private ISysAgriLimitBakService sysAgriLimitBakService;
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:limit_bak:list')")
@GetMapping("/list")
public TableDataInfo list(SysAgriLimitBak sysAgriLimitBak)
{
startPage();
List<SysAgriLimitBak> list = sysAgriLimitBakService.selectSysAgriLimitBakList(sysAgriLimitBak);
return getDataTable(list);
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:limit_bak:export')")
@Log(title = "大棚功能执行时间限位", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysAgriLimitBak sysAgriLimitBak)
{
List<SysAgriLimitBak> list = sysAgriLimitBakService.selectSysAgriLimitBakList(sysAgriLimitBak);
ExcelUtil<SysAgriLimitBak> util = new ExcelUtil<SysAgriLimitBak>(SysAgriLimitBak.class);
util.exportExcel(response, list, "大棚功能执行时间限位数据");
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:limit_bak:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id)
{
return success(sysAgriLimitBakService.selectSysAgriLimitBakById(id));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:limit_bak:add')")
@Log(title = "大棚功能执行时间限位", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysAgriLimitBak sysAgriLimitBak)
{
return toAjax(sysAgriLimitBakService.insertSysAgriLimitBak(sysAgriLimitBak));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:limit_bak:edit')")
@Log(title = "大棚功能执行时间限位", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysAgriLimitBak sysAgriLimitBak)
{
return toAjax(sysAgriLimitBakService.updateSysAgriLimitBak(sysAgriLimitBak));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:limit_bak:remove')")
@Log(title = "大棚功能执行时间限位", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids)
{
return toAjax(sysAgriLimitBakService.deleteSysAgriLimitBakByIds(ids));
}
@Log(title = "大棚运行时间限位查询", businessType = BusinessType.SELECT)
@GetMapping("/getAgriLimitByImei/{imei}")
public AjaxResult getAgriLimitByImei(@PathVariable String imei)
{
SysAgriLimitBak agriLimit = sysAgriLimitBakService.getAgriLimitByImei(imei);
return success(agriLimit);
}
@PreAuthorize("@ss.hasPermi('assets:limit_bak:add')")
@Log(title = "大棚功能执行时间限位", businessType = BusinessType.INSERT)
@PostMapping("/saveAgriLimit")
public AjaxResult saveAgriLimit(@RequestBody SysAgriLimitBak sysAgriLimitBak)
{
return toAjax(sysAgriLimitBakService.saveAgriLimit(sysAgriLimitBak));
}
}

View File

@ -0,0 +1,137 @@
package com.agri.system.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.agri.common.utils.StringUtils;
import com.agri.system.domain.vo.AgriTermVo;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType;
import com.agri.system.domain.SysAutoTerm;
import com.agri.system.service.ISysAutoTermService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
/**
* Controller
*
* @author lld
* @date 2026-02-27
*/
@RestController
@RequestMapping("/control/autoTerm")
public class SysAutoTermController extends BaseController
{
@Autowired
private ISysAutoTermService sysAutoTermService;
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:autoTerm:list')")
@GetMapping("/list")
public TableDataInfo list(SysAutoTerm sysAutoTerm)
{
startPage();
List<SysAutoTerm> list = sysAutoTermService.selectSysAutoTermList(sysAutoTerm);
return getDataTable(list);
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:autoTerm:export')")
@Log(title = "卷膜运行条件", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysAutoTerm sysAutoTerm)
{
List<SysAutoTerm> list = sysAutoTermService.selectSysAutoTermList(sysAutoTerm);
ExcelUtil<SysAutoTerm> util = new ExcelUtil<SysAutoTerm>(SysAutoTerm.class);
util.exportExcel(response, list, "卷膜运行条件数据");
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:autoTerm:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id)
{
return success(sysAutoTermService.selectSysAutoTermById(id));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:autoTerm:add')")
@Log(title = "卷膜运行条件", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysAutoTerm sysAutoTerm)
{
return toAjax(sysAutoTermService.insertSysAutoTerm(sysAutoTerm));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:autoTerm:edit')")
@Log(title = "卷膜运行条件", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysAutoTerm sysAutoTerm)
{
return toAjax(sysAutoTermService.updateSysAutoTerm(sysAutoTerm));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:autoTerm:remove')")
@Log(title = "卷膜运行条件", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids)
{
return toAjax(sysAutoTermService.deleteSysAutoTermByIds(ids));
}
@PreAuthorize("@ss.hasPermi('control:autoTerm:saveAgriTerm')")
@Log(title = "保存自动化条件", businessType = BusinessType.INSERT)
@PutMapping("/saveAgriTerm")
public AjaxResult saveAgriTerm(@RequestBody List<AgriTermVo> agriTerms) {
if (CollectionUtils.isEmpty(agriTerms)) {
return error("保存失败!自动化条件设置为空!");
} else {
String validateTips = sysAutoTermService.validate(agriTerms);
if (StringUtils.isNotEmpty(validateTips)) {
return error(validateTips);
}
}
sysAutoTermService.saveAgriTerm(agriTerms);
return success();
}
/**
*
* @param imei
* @return
*/
@PreAuthorize("@ss.hasPermi('control:autoTerm:query')")
@GetMapping("/getAgriTerm/{imei}")
public AjaxResult getAgriTerm(@PathVariable String imei) {
return success(sysAutoTermService.getAgriTerm(imei));
}
}

View File

@ -0,0 +1,104 @@
package com.agri.system.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType;
import com.agri.system.domain.SysDevOperLog;
import com.agri.system.service.ISysDevOperLogService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
/**
* Controller
*
* @author lld
* @date 2026-01-29
*/
@RestController
@RequestMapping("/control/controllog")
public class SysDevOperLogController extends BaseController
{
@Autowired
private ISysDevOperLogService sysDevOperLogService;
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:controllog:list')")
@GetMapping("/list")
public TableDataInfo list(SysDevOperLog sysDevOperLog)
{
startPage();
List<SysDevOperLog> list = sysDevOperLogService.selectSysDevOperLogList(sysDevOperLog);
return getDataTable(list);
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:controllog:export')")
@Log(title = "设备控制操作日志", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysDevOperLog sysDevOperLog)
{
List<SysDevOperLog> list = sysDevOperLogService.selectSysDevOperLogList(sysDevOperLog);
ExcelUtil<SysDevOperLog> util = new ExcelUtil<SysDevOperLog>(SysDevOperLog.class);
util.exportExcel(response, list, "设备控制操作日志数据");
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:controllog:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(sysDevOperLogService.selectSysDevOperLogById(id));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:controllog:add')")
@Log(title = "设备控制操作日志", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysDevOperLog sysDevOperLog)
{
return toAjax(sysDevOperLogService.insertSysDevOperLog(sysDevOperLog));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:controllog:edit')")
@Log(title = "设备控制操作日志", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysDevOperLog sysDevOperLog)
{
return toAjax(sysDevOperLogService.updateSysDevOperLog(sysDevOperLog));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:controllog:remove')")
@Log(title = "设备控制操作日志", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(sysDevOperLogService.deleteSysDevOperLogByIds(ids));
}
}

View File

@ -1,17 +1,13 @@
package com.agri.system.controller; package com.agri.system.controller;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agri.common.annotation.Log; import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController; import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult; import com.agri.common.core.domain.AjaxResult;
@ -112,4 +108,14 @@ public class SysDtuDataController extends BaseController
{ {
return toAjax(sysDtuDataService.deleteSysDtuDataByIds(ids)); return toAjax(sysDtuDataService.deleteSysDtuDataByIds(ids));
} }
@PreAuthorize("@ss.hasPermi('system:data:query')")
@GetMapping(value = "/getHistoryData")
public AjaxResult getHistoryData(@RequestParam("imei") String imei,
@RequestParam(value = "startTime",required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
@RequestParam(value = "endTime",required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime)
{
return success(sysDtuDataService.getHistoryData(imei,startTime,endTime));
}
} }

View File

@ -0,0 +1,120 @@
package com.agri.system.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.agri.system.domain.SysDtuRemark;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType;
import com.agri.system.domain.SysDtuRemarkBak;
import com.agri.system.service.ISysDtuRemarkBakService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
/**
* dtuController
*
* @author lld
* @date 2026-03-17
*/
@RestController
@RequestMapping("/assets/remark_bak")
public class SysDtuRemarkBakController extends BaseController
{
@Autowired
private ISysDtuRemarkBakService sysDtuRemarkBakService;
/**
* dtu
*/
@PreAuthorize("@ss.hasPermi('assets:remark_bak:list')")
@GetMapping("/list")
public TableDataInfo list(SysDtuRemarkBak sysDtuRemarkBak)
{
startPage();
List<SysDtuRemarkBak> list = sysDtuRemarkBakService.selectSysDtuRemarkBakList(sysDtuRemarkBak);
return getDataTable(list);
}
/**
* dtu
*/
@PreAuthorize("@ss.hasPermi('assets:remark_bak:export')")
@Log(title = "dtu设备备注", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysDtuRemarkBak sysDtuRemarkBak)
{
List<SysDtuRemarkBak> list = sysDtuRemarkBakService.selectSysDtuRemarkBakList(sysDtuRemarkBak);
ExcelUtil<SysDtuRemarkBak> util = new ExcelUtil<SysDtuRemarkBak>(SysDtuRemarkBak.class);
util.exportExcel(response, list, "dtu设备备注数据");
}
/**
* dtu
*/
@PreAuthorize("@ss.hasPermi('assets:remark_bak:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id)
{
return success(sysDtuRemarkBakService.selectSysDtuRemarkBakById(id));
}
/**
* dtu
*/
@PreAuthorize("@ss.hasPermi('assets:remark_bak:add')")
@Log(title = "dtu设备备注", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysDtuRemarkBak sysDtuRemarkBak)
{
return toAjax(sysDtuRemarkBakService.insertSysDtuRemarkBak(sysDtuRemarkBak));
}
/**
* dtu
*/
@PreAuthorize("@ss.hasPermi('assets:remark_bak:edit')")
@Log(title = "dtu设备备注", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysDtuRemarkBak sysDtuRemarkBak)
{
return toAjax(sysDtuRemarkBakService.updateSysDtuRemarkBak(sysDtuRemarkBak));
}
/**
* dtu
*/
@PreAuthorize("@ss.hasPermi('assets:remark_bak:remove')")
@Log(title = "dtu设备备注", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids)
{
return toAjax(sysDtuRemarkBakService.deleteSysDtuRemarkBakByIds(ids));
}
/**
* dtugetDtuRemarkByImei
*/
@PreAuthorize("@ss.hasPermi('assets:remark_bak:list')")
@GetMapping(value = "/getDtuRemarkByImei")
public AjaxResult getDtuRemarkByImei(@RequestParam("imei") String imei)
{
SysDtuRemarkBak dtuRemark = sysDtuRemarkBakService.getDtuRemarkByImei(imei);
return success(dtuRemark);
}
@PreAuthorize("@ss.hasPermi('assets:remark_bak:add')")
@Log(title = "dtu设备备注", businessType = BusinessType.INSERT)
@PostMapping("/saveAgriRemark")
public AjaxResult saveAgriRemark(@RequestBody SysDtuRemarkBak sysDtuRemarkBak)
{
return toAjax(sysDtuRemarkBakService.saveAgriRemark(sysDtuRemarkBak));
}
}

View File

@ -1,24 +1,23 @@
package com.agri.system.controller; package com.agri.system.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.apache.ibatis.ognl.ObjectElementsAccessor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.agri.common.annotation.Log; import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController; import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult; import com.agri.common.core.domain.AjaxResult;
import com.agri.common.core.page.TableDataInfo;
import com.agri.common.enums.BusinessType; import com.agri.common.enums.BusinessType;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.system.domain.SysDtuRemark; import com.agri.system.domain.SysDtuRemark;
import com.agri.system.service.ISysDtuRemarkService; import com.agri.system.service.ISysDtuRemarkService;
import com.agri.common.utils.poi.ExcelUtil; import org.springframework.beans.factory.annotation.Autowired;
import com.agri.common.core.page.TableDataInfo; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/** /**
* dtuController * dtuController
* *
* @author lld * @author lld
* @date 2026-01-21 * @date 2026-01-21
*/ */
@ -57,7 +56,7 @@ public class SysDtuRemarkController extends BaseController
/** /**
* dtu * dtu
*/ */
@PreAuthorize("@ss.hasPermi('assets:remark:query')") @PreAuthorize("@ss.hasPermi('assets:remark:list')")
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) public AjaxResult getInfo(@PathVariable("id") Long id)
{ {
@ -101,7 +100,7 @@ public class SysDtuRemarkController extends BaseController
/** /**
* dtu * dtu
*/ */
@PreAuthorize("@ss.hasPermi('assets:remark:query')") @PreAuthorize("@ss.hasPermi('assets:remark:list')")
@GetMapping(value = "/getDtuByImei") @GetMapping(value = "/getDtuByImei")
public AjaxResult getDtuByImei(@RequestParam("imei") String imei) public AjaxResult getDtuByImei(@RequestParam("imei") String imei)
{ {

View File

@ -0,0 +1,116 @@
package com.agri.system.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType;
import com.agri.system.domain.SysImeiAutoLog;
import com.agri.system.service.ISysImeiAutoLogService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
/**
* Controller
*
* @author lld
* @date 2026-03-29
*/
@RestController
@RequestMapping("/warn/autolog")
public class SysImeiAutoLogController extends BaseController
{
@Autowired
private ISysImeiAutoLogService sysImeiAutoLogService;
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:autolog:list')")
@GetMapping("/list")
public TableDataInfo list(SysImeiAutoLog sysImeiAutoLog)
{
startPage();
List<SysImeiAutoLog> list = sysImeiAutoLogService.selectSysImeiAutoLogList(sysImeiAutoLog);
return getDataTable(list);
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:autolog:export')")
@Log(title = "自动模式日志", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysImeiAutoLog sysImeiAutoLog)
{
List<SysImeiAutoLog> list = sysImeiAutoLogService.selectSysImeiAutoLogList(sysImeiAutoLog);
ExcelUtil<SysImeiAutoLog> util = new ExcelUtil<SysImeiAutoLog>(SysImeiAutoLog.class);
util.exportExcel(response, list, "自动模式日志数据");
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:autolog:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(sysImeiAutoLogService.selectSysImeiAutoLogById(id));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:autolog:add')")
@Log(title = "自动模式日志", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysImeiAutoLog sysImeiAutoLog)
{
return toAjax(sysImeiAutoLogService.insertSysImeiAutoLog(sysImeiAutoLog));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:autolog:edit')")
@Log(title = "自动模式日志", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysImeiAutoLog sysImeiAutoLog)
{
return toAjax(sysImeiAutoLogService.updateSysImeiAutoLog(sysImeiAutoLog));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:autolog:remove')")
@Log(title = "自动模式日志", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(sysImeiAutoLogService.deleteSysImeiAutoLogByIds(ids));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:autolog:list')")
@GetMapping("/getAutoLogList")
public AjaxResult getAutoLogList(SysImeiAutoLog sysImeiAutoLog)
{
List<SysImeiAutoLog> list = sysImeiAutoLogService.selectSysImeiAutoLogList(sysImeiAutoLog);
return success(list);
}
}

View File

@ -0,0 +1,183 @@
package com.agri.system.controller;
import java.time.LocalDate;
import java.util.List;
import java.util.Queue;
import javax.servlet.http.HttpServletResponse;
import com.agri.common.utils.DateUtils;
import com.agri.common.utils.SecurityUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType;
import com.agri.system.domain.SysMessage;
import com.agri.system.service.ISysMessageService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
/**
* Controller
*
* @author lld
* @date 2026-03-26
*/
@RestController
@RequestMapping("/warn/message")
public class SysMessageController extends BaseController
{
@Autowired
private ISysMessageService sysMessageService;
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:message:list')")
@GetMapping("/list")
public TableDataInfo list(SysMessage sysMessage)
{
startPage();
List<SysMessage> list = sysMessageService.selectSysMessageList(sysMessage);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('warn:message:list')")
@GetMapping("/getMessage")
public AjaxResult getMessage(SysMessage sysMessage)
{
List<SysMessage> list = sysMessageService.selectSysMessageList(sysMessage);
return success(list);
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:message:export')")
@Log(title = "系统消息中心", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysMessage sysMessage)
{
List<SysMessage> list = sysMessageService.selectSysMessageList(sysMessage);
ExcelUtil<SysMessage> util = new ExcelUtil<SysMessage>(SysMessage.class);
util.exportExcel(response, list, "系统消息中心数据");
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:message:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(sysMessageService.selectSysMessageById(id));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:message:add')")
@Log(title = "系统消息中心", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysMessage sysMessage)
{
return toAjax(sysMessageService.insertSysMessage(sysMessage));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:message:edit')")
@Log(title = "系统消息中心", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysMessage sysMessage)
{
return toAjax(sysMessageService.updateSysMessage(sysMessage));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:message:remove')")
@Log(title = "系统消息中心", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(sysMessageService.deleteSysMessageByIds(ids));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('warn:message:list')")
@GetMapping("/getNewMessage")
public AjaxResult getNewMessage(SysMessage sysMessage)
{
List<SysMessage> list = sysMessageService.selectSysMessageList(sysMessage);
return success(list);
}
@PreAuthorize("@ss.hasPermi('warn:message:edit')")
@Log(title = "系统消息中心", businessType = BusinessType.UPDATE)
@PutMapping("/updateRead")
public AjaxResult updateRead(@RequestBody SysMessage sysMessage)
{
if (SecurityUtils.isAdmin()) return success();
sysMessageService.lambdaUpdate()
.eq(SysMessage::getReceiver, SecurityUtils.getUserId())
.eq(SysMessage::getReadStatus, 0)
.eq(sysMessage.getId()!=null, SysMessage::getId, sysMessage.getId())
.eq(StringUtils.isNotBlank(sysMessage.getMsgType()),
SysMessage::getMsgType, sysMessage.getMsgType())
.set(SysMessage::getReadStatus, 1)
.update();
return success();
}
@PreAuthorize("@ss.hasPermi('warn:message:remove')")
@Log(title = "排量删除历史记录", businessType = BusinessType.DELETE)
@DeleteMapping("/removeBatch")
public AjaxResult removeBatch(@RequestBody SysMessage sysMessage)
{
QueryWrapper<SysMessage> wrapper = new QueryWrapper<>();
wrapper.eq("receiver", SecurityUtils.getUserId());
wrapper.eq("msg_type", sysMessage.getMsgType());
sysMessageService.remove(wrapper);
return success();
}
@PreAuthorize("@ss.hasPermi('warn:message:list')")
@GetMapping("/getMsgOverview")
public AjaxResult getMsgOverview(SysMessage sysMessage)
{
List<SysMessage> list = sysMessageService.getMsgOverview(sysMessage);
return success(list);
}
@PreAuthorize("@ss.hasPermi('warn:message:list')")
@GetMapping("/unReadCount")
public AjaxResult unReadCount()
{
LocalDate localDate = LocalDate.now().minusDays(7);
LocalDate localDate1 = LocalDate.now().plusDays(1);
Long count = sysMessageService.lambdaQuery()
.eq(SysMessage::getReceiver, SecurityUtils.getUserId())
.eq(SysMessage::getReadStatus, 0)
.geSql(SysMessage::getCreateTime, "DATE_SUB(CURDATE(), INTERVAL 7 DAY)")
.leSql(SysMessage::getCreateTime, "DATE_ADD(CURDATE(), INTERVAL 1 DAY)")
.count();
return success(count);
}
}

View File

@ -0,0 +1,104 @@
package com.agri.system.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType;
import com.agri.system.domain.SysRollerAir;
import com.agri.system.service.ISysRollerAirService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
/**
* Controller
*
* @author lld
* @date 2026-03-04
*/
@RestController
@RequestMapping("/assets/air")
public class SysRollerAirController extends BaseController
{
@Autowired
private ISysRollerAirService sysRollerAirService;
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:air:list')")
@GetMapping("/list")
public TableDataInfo list(SysRollerAir sysRollerAir)
{
startPage();
List<SysRollerAir> list = sysRollerAirService.selectSysRollerAirList(sysRollerAir);
return getDataTable(list);
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:air:export')")
@Log(title = "自动化卷膜风口大小设置", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysRollerAir sysRollerAir)
{
List<SysRollerAir> list = sysRollerAirService.selectSysRollerAirList(sysRollerAir);
ExcelUtil<SysRollerAir> util = new ExcelUtil<SysRollerAir>(SysRollerAir.class);
util.exportExcel(response, list, "自动化卷膜风口大小设置数据");
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:air:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(sysRollerAirService.selectSysRollerAirById(id));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:air:add')")
@Log(title = "自动化卷膜风口大小设置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysRollerAir sysRollerAir)
{
return toAjax(sysRollerAirService.insertSysRollerAir(sysRollerAir));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:air:edit')")
@Log(title = "自动化卷膜风口大小设置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysRollerAir sysRollerAir)
{
return toAjax(sysRollerAirService.updateSysRollerAir(sysRollerAir));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:air:remove')")
@Log(title = "自动化卷膜风口大小设置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(sysRollerAirService.deleteSysRollerAirByIds(ids));
}
}

View File

@ -0,0 +1,104 @@
package com.agri.system.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.enums.BusinessType;
import com.agri.system.domain.SysRollerParam;
import com.agri.system.service.ISysRollerParamService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
/**
* Controller
*
* @author lld
* @date 2026-02-27
*/
@RestController
@RequestMapping("/control/rollerParam")
public class SysRollerParamController extends BaseController
{
@Autowired
private ISysRollerParamService sysRollerParamService;
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:rollerParam:list')")
@GetMapping("/list")
public TableDataInfo list(SysRollerParam sysRollerParam)
{
startPage();
List<SysRollerParam> list = sysRollerParamService.selectSysRollerParamList(sysRollerParam);
return getDataTable(list);
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:rollerParam:export')")
@Log(title = "卷膜参数配置", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysRollerParam sysRollerParam)
{
List<SysRollerParam> list = sysRollerParamService.selectSysRollerParamList(sysRollerParam);
ExcelUtil<SysRollerParam> util = new ExcelUtil<SysRollerParam>(SysRollerParam.class);
util.exportExcel(response, list, "卷膜参数配置数据");
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:rollerParam:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id)
{
return success(sysRollerParamService.selectSysRollerParamById(id));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:rollerParam:add')")
@Log(title = "卷膜参数配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysRollerParam sysRollerParam)
{
return toAjax(sysRollerParamService.insertSysRollerParam(sysRollerParam));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:rollerParam:edit')")
@Log(title = "卷膜参数配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysRollerParam sysRollerParam)
{
return toAjax(sysRollerParamService.updateSysRollerParam(sysRollerParam));
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('control:rollerParam:remove')")
@Log(title = "卷膜参数配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids)
{
return toAjax(sysRollerParamService.deleteSysRollerParamByIds(ids));
}
}

View File

@ -0,0 +1,177 @@
package com.agri.system.controller;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil;
import com.agri.common.annotation.Log;
import com.agri.common.core.controller.BaseController;
import com.agri.common.core.domain.AjaxResult;
import com.agri.common.core.page.TableDataInfo;
import com.agri.common.enums.BusinessType;
import com.agri.common.utils.SecurityUtils;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysUserAgri;
import com.agri.system.service.ISysUserAgriService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.sql.Wrapper;
import java.util.List;
import java.util.Map;
/**
* -Controller
*
* @author lld
* @date 2026-01-25
*/
@RestController
@RequestMapping("/assets/userAgri")
public class SysUserAgriController extends BaseController {
@Autowired
private ISysUserAgriService sysUserAgriService;
/**
* -
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:list')")
@GetMapping("/list")
public TableDataInfo list(SysUserAgri sysUserAgri) {
startPage();
List<SysUserAgri> list = sysUserAgriService.selectSysUserAgriList(sysUserAgri);
return getDataTable(list);
}
/**
* -
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:export')")
@Log(title = "大棚信息(用户-设备关联)", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysUserAgri sysUserAgri) {
List<SysUserAgri> list = sysUserAgriService.selectSysUserAgriList(sysUserAgri);
ExcelUtil<SysUserAgri> util = new ExcelUtil<SysUserAgri>(SysUserAgri.class);
util.exportExcel(response, list, "大棚信息(用户-设备关联)数据");
}
/**
* -
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(sysUserAgriService.selectSysUserAgriById(id));
}
/**
* -
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:add')")
@Log(title = "大棚信息(用户-设备关联)", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysUserAgri sysUserAgri) {
return toAjax(sysUserAgriService.save(sysUserAgri));
}
/**
* -
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:edit')")
@Log(title = "大棚信息(用户-设备关联)", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysUserAgri sysUserAgri) {
return toAjax(sysUserAgriService.updateSysUserAgri(sysUserAgri));
}
/**
* -
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:remove')")
@Log(title = "大棚信息(用户-设备关联)", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(sysUserAgriService.deleteSysUserAgriByIds(ids));
}
@PreAuthorize("@ss.hasPermi('assets:userAgri:list')")
@GetMapping("/findAgriUser")
public TableDataInfo findAgriUser(SysUserAgri sysUserAgri) {
startPage();
List<SysUserAgri> list = sysUserAgriService.findAgriUser(sysUserAgri);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('assets:userAgri:list')")
@GetMapping("/findAllUser")
public TableDataInfo findAllUser(SysUserAgri sysUserAgri) {
List<SysUserAgri> list = sysUserAgriService.findAllUser(sysUserAgri);
return getDataTable(list);
}
/**
* -
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:add')")
@Log(title = "大棚信息(用户-设备批量关联)", businessType = BusinessType.INSERT)
@PostMapping("/batchAssociaUser")
public AjaxResult batchAssociaUser(@RequestBody List<SysUserAgri> userList) {
if (CollectionUtils.isNotEmpty(userList) && !userList.isEmpty()) {
String agriId = userList.get(0).getAgriId();
if (ObjectUtils.isNotEmpty(agriId)) {
QueryWrapper<SysUserAgri> wrapper = new QueryWrapper<>();
wrapper.eq("agri_id",agriId);
sysUserAgriService.remove(wrapper);
boolean saveBatch = sysUserAgriService.saveBatch(userList);
if (!saveBatch) {
return error("更新用户失败");
}
}
}
return success();
}
/**
*
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:remove')")
@Log(title = "大棚管理", businessType = BusinessType.DELETE)
@DeleteMapping("/removeAgri")
public AjaxResult removeAgri(@RequestBody SysUserAgri userAgri)
{
if (!SecurityUtils.isAdmin()) {
QueryWrapper<SysUserAgri> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("agri_id", userAgri.getImei());
queryWrapper.eq("user_id", SecurityUtils.getUserId());
boolean remove = sysUserAgriService.remove(queryWrapper);
}
return success();
}
/**
*
* @param sysUserAgri
* @return
*/
@PreAuthorize("@ss.hasPermi('assets:userAgri:list')")
@GetMapping("/getRecentShareUser")
public AjaxResult getRecentShareUser(SysUserAgri sysUserAgri) {
return success(sysUserAgriService.getRecentShareUser(sysUserAgri));
}
}

View File

@ -3,10 +3,15 @@ package com.agri.system.domain;
import com.agri.common.annotation.Excel; import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity; import com.agri.common.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.models.auth.In;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
@ -19,6 +24,7 @@ import java.util.Date;
* @author agri * @author agri
* @date 2026-01-08 * @date 2026-01-08
*/ */
@Data
@TableName("sys_agri_info") @TableName("sys_agri_info")
public class SysAgriInfo extends BaseEntity public class SysAgriInfo extends BaseEntity
{ {
@ -37,198 +43,92 @@ public class SysAgriInfo extends BaseEntity
@Excel(name = "大棚名称") @Excel(name = "大棚名称")
private String agriName; private String agriName;
/** 工作模式 */
@Excel(name = "工作模式")
private Integer workMode;
/** 关联用户ID */ /** 关联用户ID */
@Excel(name = "关联用户ID") @Excel(name = "关联用户ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long userId; private Long userId;
/** 温度上限(℃) */
@Excel(name = "温度上限(℃)")
private BigDecimal tempUpper;
/** 温度下限(℃) */
@Excel(name = "温度下限(℃)")
private BigDecimal tempLower;
/** 湿度上限(%RH) */
@Excel(name = "湿度上限(%RH)")
private BigDecimal humiUpper;
/** 湿度下限(%RH) */
@Excel(name = "湿度下限(%RH)")
private BigDecimal humiLower;
/** 告警开关(0-关闭1-开启) */ /** 告警开关(0-关闭1-开启) */
@Excel(name = "告警开关(0-关闭1-开启)") @Excel(name = "告警开关(0-关闭1-开启)")
private Integer alarmStatus; private Integer alarmStatus;
@TableField(exist = false)
/** 设备状态(0-离线1-在线2-故障) */ /** 设备状态(0-离线1-在线2-故障) */
@Excel(name = "设备状态(0-离线1-在线2-故障)") @Excel(name = "设备状态(0-离线1-在线2-故障)")
private Integer deviceStatus; private Integer deviceStatus;
/** 安装时间 */ /** 安装时间 */
@Getter
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "安装时间", width = 30, dateFormat = "yyyy-MM-dd") @Excel(name = "安装时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date installTime; private Date installTime;
/** 安装位置 */ /** 安装位置 */
@Setter
@Excel(name = "安装位置") @Excel(name = "安装位置")
private String location; private String location;
/** 逻辑删除(0-未删1-已删) */ /** 逻辑删除(0-未删1-已删) */
@Excel(name = "逻辑删除(0-未删1-已删)") @Excel(name = "逻辑删除(0-未删1-已删)")
@TableLogic(value = "0", delval = "1")
private Integer isDeleted; private Integer isDeleted;
public void setId(Long id) /**
{ * 01
this.id = id; */
} @TableField(exist = false)
private Integer sourceCode;
public Long getId() /**
{ *
return id; */
} private Integer quiltNum;
public void setImei(String imei) /**
{ *
this.imei = imei; */
} private Integer filmNum;
public String getImei() /**
{ *
return imei; */
} private Integer blindNum;
public void setAgriName(String agriName) /** 温度上限(℃) */
{ @Excel(name = "温度上限(℃)")
this.agriName = agriName; private BigDecimal tempUp;
}
public String getAgriName() /** 温度下限(℃) */
{ @Excel(name = "温度下限(℃)")
return agriName; private BigDecimal tempLow;
}
public void setUserId(Long userId) /** 湿度上限(%RH) */
{ @Excel(name = "湿度上限(%RH)")
this.userId = userId; private BigDecimal humiUp;
}
public Long getUserId() /** 湿度下限(%RH) */
{ @Excel(name = "湿度下限(%RH)")
return userId; private BigDecimal humiLow;
}
public void setTempUpper(BigDecimal tempUpper) @TableField(exist = false)
{ private String title;
this.tempUpper = tempUpper;
}
public BigDecimal getTempUpper() @TableField(exist = false)
{ private String msg;
return tempUpper;
}
public void setTempLower(BigDecimal tempLower) @TableField(exist = false)
{ private Integer status;
this.tempLower = tempLower;
}
public BigDecimal getTempLower() /** 关联用户ID */
{ @TableField(exist = false)
return tempLower; @JsonSerialize(using = ToStringSerializer.class)
} private Long inviteBy;
public void setHumiUpper(BigDecimal humiUpper) /** 已分享次数(非数据库字段,仅用于查询返回) */
{ @TableField(exist = false)
this.humiUpper = humiUpper; private Integer sharedCount;
}
public BigDecimal getHumiUpper()
{
return humiUpper;
}
public void setHumiLower(BigDecimal humiLower)
{
this.humiLower = humiLower;
}
public BigDecimal getHumiLower()
{
return humiLower;
}
public void setAlarmStatus(Integer alarmStatus)
{
this.alarmStatus = alarmStatus;
}
public Integer getAlarmStatus()
{
return alarmStatus;
}
public void setDeviceStatus(Integer deviceStatus)
{
this.deviceStatus = deviceStatus;
}
public Integer getDeviceStatus()
{
return deviceStatus;
}
public void setInstallTime(Date installTime)
{
this.installTime = installTime;
}
public Date getInstallTime()
{
return installTime;
}
public void setLocation(String location)
{
this.location = location;
}
public String getLocation()
{
return location;
}
public void setIsDeleted(Integer isDeleted)
{
this.isDeleted = isDeleted;
}
public Integer getIsDeleted()
{
return isDeleted;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("imei", getImei())
.append("agriName", getAgriName())
.append("userId", getUserId())
.append("tempUpper", getTempUpper())
.append("tempLower", getTempLower())
.append("humiUpper", getHumiUpper())
.append("humiLower", getHumiLower())
.append("alarmStatus", getAlarmStatus())
.append("deviceStatus", getDeviceStatus())
.append("installTime", getInstallTime())
.append("location", getLocation())
.append("remark", getRemark())
.append("createTime", getCreateTime())
.append("createBy", getCreateBy())
.append("updateTime", getUpdateTime())
.append("updateBy", getUpdateBy())
.append("isDeleted", getIsDeleted())
.toString();
}
} }

View File

@ -50,6 +50,14 @@ public class SysAgriLimit extends BaseEntity
@Excel(name = "卷被关限位时间(秒)") @Excel(name = "卷被关限位时间(秒)")
private String jbgLimit; private String jbgLimit;
/** 卷被开限位时间(秒) */
@Excel(name = "卷帘开限位时间(秒)")
private String jlkLimit;
/** 卷被关限位时间(秒) */
@Excel(name = "卷帘关限位时间(秒)")
private String jlgLimit;
/** 卷膜1开限位时间(秒) */ /** 卷膜1开限位时间(秒) */
@Excel(name = "卷膜1开限位时间(秒)") @Excel(name = "卷膜1开限位时间(秒)")
private String jm1kLimit; private String jm1kLimit;
@ -88,7 +96,23 @@ public class SysAgriLimit extends BaseEntity
@Excel(name = "删除时间", width = 30, dateFormat = "yyyy-MM-dd") @Excel(name = "删除时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date deleteTime; private Date deleteTime;
public void setId(String id) public String getJlkLimit() {
return jlkLimit;
}
public void setJlkLimit(String jlkLimit) {
this.jlkLimit = jlkLimit;
}
public String getJlgLimit() {
return jlgLimit;
}
public void setJlgLimit(String jlgLimit) {
this.jlgLimit = jlgLimit;
}
public void setId(String id)
{ {
this.id = id; this.id = id;
} }
@ -233,6 +257,8 @@ public class SysAgriLimit extends BaseEntity
.append("imei", getImei()) .append("imei", getImei())
.append("jbkLimit", getJbkLimit()) .append("jbkLimit", getJbkLimit())
.append("jbgLimit", getJbgLimit()) .append("jbgLimit", getJbgLimit())
.append("jlkLimit", getJlkLimit())
.append("jlgLimit", getJlgLimit())
.append("jm1kLimit", getJm1kLimit()) .append("jm1kLimit", getJm1kLimit())
.append("jm1gLimit", getJm1gLimit()) .append("jm1gLimit", getJm1gLimit())
.append("jm2kLimit", getJm2kLimit()) .append("jm2kLimit", getJm2kLimit())

View File

@ -0,0 +1,154 @@
package com.agri.system.domain;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.baomidou.mybatisplus.annotation.TableName;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
/**
* sys_agri_limit_bak
*
* @author lld
* @date 2026-03-17
*/
@TableName("sys_agri_limit_bak")
public class SysAgriLimitBak extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private String id;
/** 大棚ID */
@Excel(name = "大棚ID")
private Long agriId;
/** 大棚名称 */
@Excel(name = "大棚名称")
private String agriName;
/** imei */
@Excel(name = "imei")
private String imei;
/** 卷膜 */
@Excel(name = "卷膜")
private String roller;
/** 限位时间 */
@Excel(name = "限位时间")
private BigDecimal funcLimit;
/** 数据版本号(乐观锁) */
@Excel(name = "数据版本号", readConverterExp = "乐=观锁")
@Version
private String version;
@TableField(exist = false)
private Map<String, BigDecimal> limitMaps;
public void setId(String id)
{
this.id = id;
}
public String getId()
{
return id;
}
public void setAgriId(Long agriId)
{
this.agriId = agriId;
}
public Long getAgriId()
{
return agriId;
}
public void setAgriName(String agriName)
{
this.agriName = agriName;
}
public String getAgriName()
{
return agriName;
}
public void setImei(String imei)
{
this.imei = imei;
}
public String getImei()
{
return imei;
}
public void setRoller(String roller)
{
this.roller = roller;
}
public String getRoller()
{
return roller;
}
public BigDecimal getFuncLimit() {
return funcLimit;
}
public void setFuncLimit(BigDecimal funcLimit) {
this.funcLimit = funcLimit;
}
public void setVersion(String version)
{
this.version = version;
}
public String getVersion()
{
return version;
}
public Map<String, BigDecimal> getLimitMaps() {
return limitMaps;
}
public void setLimitMaps(Map<String, BigDecimal> limitMaps) {
this.limitMaps = limitMaps;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("agriId", getAgriId())
.append("agriName", getAgriName())
.append("imei", getImei())
.append("roller", getRoller())
.append("remark", getRemark())
.append("version", getVersion())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.toString();
}
}

View File

@ -0,0 +1,145 @@
package com.agri.system.domain;
import java.math.BigDecimal;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.baomidou.mybatisplus.annotation.TableName;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
/**
* sys_auto_term
*
* @author lld
* @date 2026-02-27
*/
@TableName("sys_auto_term")
public class SysAutoTerm extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private String id;
/** imei */
@Excel(name = "imei")
private String imei;
/** 卷膜标识 */
@Excel(name = "卷膜标识")
private String roller;
/** 运行时间始 */
@JsonFormat(pattern = "HH:mm",timezone = "GMT+8")
@Excel(name = "运行时间始", width = 30)
private Date startTime;
/** 运行时间止 */
@JsonFormat(pattern = "HH:mm",timezone = "GMT+8")
@Excel(name = "运行时间止", width = 30)
private Date endTime;
/** 适宜温度 */
@Excel(name = "适宜温度")
private BigDecimal temp;
/** 开启风口大小(米) */
@Excel(name = "开启风口大小(米)")
private Long vent;
public void setId(String id)
{
this.id = id;
}
public String getId()
{
return id;
}
public void setImei(String imei)
{
this.imei = imei;
}
public String getImei()
{
return imei;
}
public void setRoller(String roller)
{
this.roller = roller;
}
public String getRoller()
{
return roller;
}
public void setStartTime(Date startTime)
{
this.startTime = startTime;
}
public Date getStartTime()
{
return startTime;
}
public void setEndTime(Date endTime)
{
this.endTime = endTime;
}
public Date getEndTime()
{
return endTime;
}
public void setTemp(BigDecimal temp)
{
this.temp = temp;
}
public BigDecimal getTemp()
{
return temp;
}
public void setVent(Long vent)
{
this.vent = vent;
}
public Long getVent()
{
return vent;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("imei", getImei())
.append("roller", getRoller())
.append("startTime", getStartTime())
.append("endTime", getEndTime())
.append("temp", getTemp())
.append("vent", getVent())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.toString();
}
}

View File

@ -0,0 +1,309 @@
package com.agri.system.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Builder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.baomidou.mybatisplus.annotation.TableName;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
/**
* sys_dev_oper_log
*
* @author lld
* @date 2026-01-29
*/
@TableName("sys_dev_oper_log")
public class SysDevOperLog extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 大棚名称 */
@Excel(name = "大棚名称")
private String agriName;
/** imei */
@Excel(name = "imei")
private String imei;
/** 功能码 */
@Excel(name = "功能码")
private String funcCode;
/** 操作类型 */
@Excel(name = "操作类型")
private Integer opType;
/** 操作来源 */
@Excel(name = "操作来源")
private Integer opSource;
/** 指令 */
@Excel(name = "指令")
private String payload;
/** 是否成功获取锁 */
@Excel(name = "是否成功获取锁")
private Integer lockAcquired;
/** 锁持有者 */
@Excel(name = "锁持有者")
private String lockHolder;
/** 当前任务队列情况 */
@Excel(name = "当前任务队列情况")
private String taskStatus;
/** 是否收到设备回执 */
@Excel(name = "是否收到设备回执")
private Integer ackReceived;
/** 设备控制是否成功 */
@Excel(name = "设备控制是否成功")
private Integer ackSuc;
/** 设备控制锁是否释放成功 */
@Excel(name = "设备控制锁是否释放成功")
private Integer isLockSuc;
/** 是否触发定时任务 */
@Excel(name = "是否触发定时任务")
private Integer isTask;
@Excel(name = "大棚运行时间限位")
private Integer runTime;
/** 未触发原因 */
@Excel(name = "未触发原因")
private String noTaskReason;
/** 设备回执 */
@Excel(name = "设备回执")
private String ack;
/** 自动关任务最终执行结果 */
@Excel(name = "自动关任务最终执行结果")
private Integer execResult;
/** 未执行原因锁占用、无最新状态、JSON异常 */
@Excel(name = "未执行原因", readConverterExp = "如=锁占用、无最新状态、JSON异常")
private String skipReason;
/** 执行前读取的状态快照 */
@Excel(name = "执行前读取的状态快照")
private String latestState;
/** 乐观锁版本号 */
@Version
private Long version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAgriName() {
return agriName;
}
public void setAgriName(String agriName) {
this.agriName = agriName;
}
public String getImei() {
return imei;
}
public void setImei(String imei) {
this.imei = imei;
}
public String getFuncCode() {
return funcCode;
}
public void setFuncCode(String funcCode) {
this.funcCode = funcCode;
}
public Integer getOpType() {
return opType;
}
public void setOpType(Integer opType) {
this.opType = opType;
}
public Integer getOpSource() {
return opSource;
}
public void setOpSource(Integer opSource) {
this.opSource = opSource;
}
public String getPayload() {
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
public Integer getLockAcquired() {
return lockAcquired;
}
public void setLockAcquired(Integer lockAcquired) {
this.lockAcquired = lockAcquired;
}
public String getLockHolder() {
return lockHolder;
}
public void setLockHolder(String lockHolder) {
this.lockHolder = lockHolder;
}
public String getTaskStatus() {
return taskStatus;
}
public void setTaskStatus(String taskStatus) {
this.taskStatus = taskStatus;
}
public Integer getAckReceived() {
return ackReceived;
}
public void setAckReceived(Integer ackReceived) {
this.ackReceived = ackReceived;
}
public Integer getAckSuc() {
return ackSuc;
}
public void setAckSuc(Integer ackSuc) {
this.ackSuc = ackSuc;
}
public Integer getIsLockSuc() {
return isLockSuc;
}
public void setIsLockSuc(Integer isLockSuc) {
this.isLockSuc = isLockSuc;
}
public Integer getIsTask() {
return isTask;
}
public void setIsTask(Integer isTask) {
this.isTask = isTask;
}
public String getNoTaskReason() {
return noTaskReason;
}
public void setNoTaskReason(String noTaskReason) {
this.noTaskReason = noTaskReason;
}
public String getAck() {
return ack;
}
public void setAck(String ack) {
this.ack = ack;
}
public Integer getExecResult() {
return execResult;
}
public void setExecResult(Integer execResult) {
this.execResult = execResult;
}
public String getSkipReason() {
return skipReason;
}
public void setSkipReason(String skipReason) {
this.skipReason = skipReason;
}
public String getLatestState() {
return latestState;
}
public void setLatestState(String latestState) {
this.latestState = latestState;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Integer getRunTime() {
return runTime;
}
public void setRunTime(Integer runTime) {
this.runTime = runTime;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("agriName", getAgriName())
.append("imei", getImei())
.append("funcCode", getFuncCode())
.append("opType", getOpType())
.append("opSource", getOpSource())
.append("payload", getPayload())
.append("lockAcquired", getLockAcquired())
.append("lockHolder", getLockHolder())
.append("taskStatus", getTaskStatus())
.append("ackReceived", getAckReceived())
.append("ackSuc", getAckSuc())
.append("isLockSuc", getIsLockSuc())
.append("isTask", getIsTask())
.append("runTime", getRunTime())
.append("noTaskReason", getNoTaskReason())
.append("ack", getAck())
.append("execResult", getExecResult())
.append("skipReason", getSkipReason())
.append("latestState", getLatestState())
.append("remark", getRemark())
.append("version", getVersion())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.toString();
}
}

View File

@ -9,13 +9,14 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.time.LocalDateTime;
/** /**
* DTU湿 sys_dtu_data * DTU湿 sys_dtu_data
* *
* @author agri * @author agri
* @date 2025-12-23 * @LocalDateTime 2025-12-23
*/ */
public class SysDtuData extends BaseEntity public class SysDtuData extends BaseEntity
{ {
@ -36,7 +37,7 @@ public class SysDtuData extends BaseEntity
/** ts转换后的正常时间(由服务端转换入库) */ /** ts转换后的正常时间(由服务端转换入库) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "ts转换后的正常时间(由服务端转换入库)", width = 30, dateFormat = "yyyy-MM-dd") @Excel(name = "ts转换后的正常时间(由服务端转换入库)", width = 30, dateFormat = "yyyy-MM-dd")
private Date time; private LocalDateTime time;
/** 温度1(℃) */ /** 温度1(℃) */
@Excel(name = "温度1(℃)") @Excel(name = "温度1(℃)")
@ -74,132 +75,132 @@ public class SysDtuData extends BaseEntity
@Excel(name = "原始JSON元信息(原始上报码/额外字段)") @Excel(name = "原始JSON元信息(原始上报码/额外字段)")
private String raw; private String raw;
public void setId(Long id) public void setId(Long id)
{ {
this.id = id; this.id = id;
} }
public Long getId() public Long getId()
{ {
return id; return id;
} }
public void setImei(String imei) public void setImei(String imei)
{ {
this.imei = imei; this.imei = imei;
} }
public String getImei() public String getImei()
{ {
return imei; return imei;
} }
public void setTs(Long ts) public void setTs(Long ts)
{ {
this.ts = ts; this.ts = ts;
} }
public Long getTs() public Long getTs()
{ {
return ts; return ts;
} }
public void setTime(Date time) public void setTime(LocalDateTime time)
{ {
this.time = time; this.time = time;
} }
public Date getTime() public LocalDateTime getTime()
{ {
return time; return time;
} }
public void setTemp1(BigDecimal temp1) public void setTemp1(BigDecimal temp1)
{ {
this.temp1 = temp1; this.temp1 = temp1;
} }
public BigDecimal getTemp1() public BigDecimal getTemp1()
{ {
return temp1; return temp1;
} }
public void setHumi1(BigDecimal humi1) public void setHumi1(BigDecimal humi1)
{ {
this.humi1 = humi1; this.humi1 = humi1;
} }
public BigDecimal getHumi1() public BigDecimal getHumi1()
{ {
return humi1; return humi1;
} }
public void setTemp2(BigDecimal temp2) public void setTemp2(BigDecimal temp2)
{ {
this.temp2 = temp2; this.temp2 = temp2;
} }
public BigDecimal getTemp2() public BigDecimal getTemp2()
{ {
return temp2; return temp2;
} }
public void setHumi2(BigDecimal humi2) public void setHumi2(BigDecimal humi2)
{ {
this.humi2 = humi2; this.humi2 = humi2;
} }
public BigDecimal getHumi2() public BigDecimal getHumi2()
{ {
return humi2; return humi2;
} }
public void setTemp3(BigDecimal temp3) public void setTemp3(BigDecimal temp3)
{ {
this.temp3 = temp3; this.temp3 = temp3;
} }
public BigDecimal getTemp3() public BigDecimal getTemp3()
{ {
return temp3; return temp3;
} }
public void setHumi3(BigDecimal humi3) public void setHumi3(BigDecimal humi3)
{ {
this.humi3 = humi3; this.humi3 = humi3;
} }
public BigDecimal getHumi3() public BigDecimal getHumi3()
{ {
return humi3; return humi3;
} }
public void setTemp4(BigDecimal temp4) public void setTemp4(BigDecimal temp4)
{ {
this.temp4 = temp4; this.temp4 = temp4;
} }
public BigDecimal getTemp4() public BigDecimal getTemp4()
{ {
return temp4; return temp4;
} }
public void setHumi4(BigDecimal humi4) public void setHumi4(BigDecimal humi4)
{ {
this.humi4 = humi4; this.humi4 = humi4;
} }
public BigDecimal getHumi4() public BigDecimal getHumi4()
{ {
return humi4; return humi4;
} }
public void setRaw(String raw) public void setRaw(String raw)
{ {
this.raw = raw; this.raw = raw;
} }
public String getRaw() public String getRaw()
{ {
return raw; return raw;
} }

View File

@ -36,6 +36,14 @@ public class SysDtuRemark extends BaseEntity
@Excel(name = "卷被关") @Excel(name = "卷被关")
private String jbg; private String jbg;
/** 卷被开 */
@Excel(name = "卷帘开")
private String jlk;
/** 卷被关 */
@Excel(name = "卷帘关")
private String jlg;
/** 卷膜1开 */ /** 卷膜1开 */
@Excel(name = "卷膜1开") @Excel(name = "卷膜1开")
private String jm1k; private String jm1k;
@ -300,11 +308,29 @@ public class SysDtuRemark extends BaseEntity
return extJson; return extJson;
} }
public String getJlk() {
return jlk;
}
public void setJlk(String jlk) {
this.jlk = jlk;
}
public String getJlg() {
return jlg;
}
public void setJlg(String jlg) {
this.jlg = jlg;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("imei", getImei()) .append("imei", getImei())
.append("jlk", getJlk())
.append("jlg", getJlg())
.append("jbk", getJbk()) .append("jbk", getJbk())
.append("jbg", getJbg()) .append("jbg", getJbg())
.append("jm1k", getJm1k()) .append("jm1k", getJm1k())

View File

@ -0,0 +1,139 @@
package com.agri.system.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.baomidou.mybatisplus.annotation.TableName;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
import java.math.BigDecimal;
import java.util.Map;
/**
* dtu sys_dtu_remark_bak
*
* @author lld
* @date 2026-03-17
*/
@TableName("sys_dtu_remark_bak")
public class SysDtuRemarkBak extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private String id;
/** imei */
@Excel(name = "imei")
private String imei;
/** 类型 */
@Excel(name = "类型")
private String roller;
/** 备注 */
@Excel(name = "备注")
private String remarkShort;
/** 数据版本号(乐观锁) */
@Excel(name = "数据版本号", readConverterExp = "乐=观锁")
@Version
private String version;
/** 扩展备注 */
private String extJson;
@TableField(exist = false)
private Map<String, String> remarkMaps;
public void setId(String id)
{
this.id = id;
}
public String getId()
{
return id;
}
public void setImei(String imei)
{
this.imei = imei;
}
public String getImei()
{
return imei;
}
public void setRoller(String roller)
{
this.roller = roller;
}
public String getRoller()
{
return roller;
}
public void setRemarkShort(String remarkShort)
{
this.remarkShort = remarkShort;
}
public String getRemarkShort()
{
return remarkShort;
}
public void setVersion(String version)
{
this.version = version;
}
public String getVersion()
{
return version;
}
public void setExtJson(String extJson)
{
this.extJson = extJson;
}
public String getExtJson()
{
return extJson;
}
public Map<String, String> getRemarkMaps() {
return remarkMaps;
}
public void setRemarkMaps(Map<String, String> remarkMaps) {
this.remarkMaps = remarkMaps;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("imei", getImei())
.append("roller", getRoller())
.append("remarkShort", getRemarkShort())
.append("version", getVersion())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("extJson", getExtJson())
.append("remark", getRemark())
.toString();
}
}

View File

@ -0,0 +1,210 @@
package com.agri.system.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.baomidou.mybatisplus.annotation.TableName;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
import org.springframework.format.annotation.DateTimeFormat;
/**
* sys_imei_auto_log
*
* @author lld
* @date 2026-03-29
*/
@TableName("sys_imei_auto_log")
public class SysImeiAutoLog extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 主键 */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 设备IMEI */
@Excel(name = "设备IMEI")
private String imei;
/** 卷膜 */
@Excel(name = "卷膜")
private String rollFilm;
/** 监控时间段 */
@Excel(name = "监控时间段")
private String monitorPeriod;
/** 当前温度 */
@Excel(name = "当前温度")
private BigDecimal currentTemp;
/** 参考温度 */
@Excel(name = "参考温度")
private String refTemp;
/** 适宜温度 */
@Excel(name = "适宜温度")
private BigDecimal suitableTemp;
/** 运行风口 */
@Excel(name = "运行风口")
private BigDecimal fanStatus;
/** 是否最后一条 0否1是 */
@Excel(name = "是否最后一条 0否1是")
private int isLast;
/** 执行指令 */
@Excel(name = "执行指令")
private String execCmd;
/** 操作日志ID */
@Excel(name = "操作日志ID")
private Long logId;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
private LocalDate monitorDate;
public LocalDate getMonitorDate() {
return monitorDate;
}
public void setMonitorDate(LocalDate monitorDate) {
this.monitorDate = monitorDate;
}
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setImei(String imei)
{
this.imei = imei;
}
public String getImei()
{
return imei;
}
public void setRollFilm(String rollFilm)
{
this.rollFilm = rollFilm;
}
public String getRollFilm()
{
return rollFilm;
}
public void setMonitorPeriod(String monitorPeriod)
{
this.monitorPeriod = monitorPeriod;
}
public String getMonitorPeriod()
{
return monitorPeriod;
}
public void setCurrentTemp(BigDecimal currentTemp)
{
this.currentTemp = currentTemp;
}
public BigDecimal getCurrentTemp()
{
return currentTemp;
}
public String getRefTemp() {
return refTemp;
}
public void setRefTemp(String refTemp) {
this.refTemp = refTemp;
}
public BigDecimal getSuitableTemp() {
return suitableTemp;
}
public void setSuitableTemp(BigDecimal suitableTemp) {
this.suitableTemp = suitableTemp;
}
public BigDecimal getFanStatus() {
return fanStatus;
}
public void setFanStatus(BigDecimal fanStatus) {
this.fanStatus = fanStatus;
}
public int getIsLast() {
return isLast;
}
public void setIsLast(int isLast) {
this.isLast = isLast;
}
public void setExecCmd(String execCmd)
{
this.execCmd = execCmd;
}
public String getExecCmd()
{
return execCmd;
}
public void setLogId(Long logId)
{
this.logId = logId;
}
public Long getLogId()
{
return logId;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("imei", getImei())
.append("rollFilm", getRollFilm())
.append("monitorPeriod", getMonitorPeriod())
.append("currentTemp", getCurrentTemp())
.append("refTemp", getRefTemp())
.append("suitableTemp", getSuitableTemp())
.append("fanStatus", getFanStatus())
.append("isLast", getIsLast())
.append("execCmd", getExecCmd())
.append("logId", getLogId())
.append("remark", getRemark())
.append("createTime", getCreateTime())
.append("createBy", getCreateBy())
.append("updateTime", getUpdateTime())
.append("updateBy", getUpdateBy())
.toString();
}
}

View File

@ -0,0 +1,88 @@
package com.agri.system.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.baomidou.mybatisplus.annotation.TableName;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.util.List;
/**
* sys_message
*
* @author lld
* @date 2026-03-26
*/
@Data
@TableName("sys_message")
public class SysMessage extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 消息主键ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String imei;
/** 接收人all=全体用户,其他=用户ID */
@Excel(name = "接收人all=全体用户,其他=用户ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long receiver;
/** 消息标题 */
@Excel(name = "消息标题")
private String title;
/** 消息类型activity-活动 notice-通知 interact-互动消息 */
@Excel(name = "消息类型status-状态notice-通知event-活动")
private String msgType;
/** 阅读状态0-未读 1-已读 */
@Excel(name = "阅读状态0-未读 1-已读")
private Long readStatus;
/** 消息内容(普通文本) */
@Excel(name = "消息内容", readConverterExp = "普=通文本")
private String content;
/** 富文本内容 */
@Excel(name = "富文本内容")
private String richContent;
/** 图片地址 */
@Excel(name = "图片地址")
private String imgUrl;
/** 跳转链接地址 */
@Excel(name = "跳转链接地址")
private String linkUrl;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
private LocalDate monitorDate;
@TableField(exist = false)
private List<String> imeiList;
@TableField(exist = false)
@JsonSerialize(using = ToStringSerializer.class)
private Long sortOldId;
@TableField(exist = false)
@JsonSerialize(using = ToStringSerializer.class)
private Long sortNewId;
@TableField(exist = false)
private Integer unreadCount;
}

View File

@ -0,0 +1,78 @@
package com.agri.system.domain;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.time.LocalDateTime;
/**
* sys_roller_air
*
* @author lld
* @date 2026-03-04
*/
@Data
@TableName("sys_roller_air")
public class SysRollerAir extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 设备IMEI码 */
@Excel(name = "设备IMEI码")
private String imei;
/** 卷膜器编号/标识 */
@Excel(name = "卷膜器编号/标识")
private String roller;
/** 操作类型 0-停止 1-运行 2-查询 3-重置 */
@Excel(name = "操作类型 0-停止 1-运行 2-查询 3-重置")
private Integer opType;
/** 操作参数(JSON格式存储风口大小等配置) */
@Excel(name = "操作参数(JSON格式存储风口大小等配置)")
private String payload;
/** 客户端ID */
@Excel(name = "客户端ID")
private String clientid;
/** 操作执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "操作执行时间", width = 30, dateFormat = "yyyy-MM-dd")
private LocalDateTime opTime;
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("imei", getImei())
.append("roller", getRoller())
.append("opType", getOpType())
.append("payload", getPayload())
.append("clientid", getClientid())
.append("opTime", getOpTime())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.toString();
}
}

View File

@ -0,0 +1,82 @@
package com.agri.system.domain;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.math.BigDecimal;
/**
* sys_roller_param
*
* @author lld
* @date 2026-02-27
*/
@Data
@TableName("sys_roller_param")
public class SysRollerParam extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private String id;
/** imei */
@Excel(name = "imei")
private String imei;
/** 卷膜标识 */
@Excel(name = "卷膜标识")
private String roller;
/** 参考温度 */
@Excel(name = "参考温度")
private String refTempCode;
/** 参考温度 */
@TableField(exist = false)
@Excel(name = "参考温度")
private String refTemp;
/** 预留风口长度(cm) */
@Excel(name = "预留风口长度(cm)")
private BigDecimal reservedLen;
/** 手动计算风口总长(cm) */
@Excel(name = "手动计算风口总长(cm)")
private BigDecimal manualTotalLen;
/** 自动计算风口总长(cm) */
@Excel(name = "自动计算风口总长(cm)")
private BigDecimal autoTotalLen;
@Excel(name = "最终风口总长")
private BigDecimal ventTotalLen;
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("imei", getImei())
.append("roller", getRoller())
.append("refTempCode", getRefTempCode())
.append("reservedLen", getReservedLen())
.append("manualTotalLen", getManualTotalLen())
.append("autoTotalLen", getAutoTotalLen())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.toString();
}
}

View File

@ -0,0 +1,102 @@
package com.agri.system.domain;
import com.agri.common.annotation.Excel;
import com.agri.common.core.domain.BaseEntity;
import com.agri.common.core.domain.entity.SysUser;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.Date;
import java.util.List;
/**
* - sys_user_agri
*
* @author lld
* @date 2026-01-25
*/
@Data
@TableName("sys_user_agri")
public class SysUserAgri extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
/** 主键ID雪花 */
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 大棚ID */
@Excel(name = "大棚ID")
private String agriId;
/** 大棚名称 */
@Excel(name = "大棚名称")
private String agriName;
/** 协同用户ID */
@Excel(name = "协同用户ID")
private Long userId;
/** 角色3-OWNER/2-ADMIN/1-OPERATOR/0-VIEWER */
@Excel(name = "角色3-OWNER/2-ADMIN/1-OPERATOR/0-VIEWER")
private Integer role;
/** 权限位位运算1=查看 2=控制 4=配置 8=分享/成员管理 16=导出等 */
@Excel(name = "权限位", readConverterExp = "位=运算")
private Integer permMask;
/** 状态0=禁用 1=启用 2=待接受邀请 */
@Excel(name = "状态0=禁用 1=启用 2=待接受邀请")
private Integer status;
/** 邀请人用户ID */
@Excel(name = "邀请人用户ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long inviteBy;
/** 邀请时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "邀请时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date inviteTime;
/** 接受时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "接受时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date acceptTime;
/** 版本号(乐观锁) */
@Excel(name = "版本号", readConverterExp = "乐=观锁")
@Version
private Integer version;
@TableField(exist = false)
private SysUser sysUser;
@TableField(exist = false)
private String imei;
@TableField(exist = false)
private Boolean disabled;
@TableField(exist = false)
private List<String> imeiList;
public SysUserAgri(List<String> imeiList, Integer status) {
this.imeiList = imeiList;
this.status = status;
}
public SysUserAgri() {
}
}

View File

@ -0,0 +1,49 @@
package com.agri.system.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* @ClassName AgriAutoInfoVo
* @Description TODO
* @Author lld
* @Date 2026/3/5 22:07
* @Version 1.0
*/
@Data
public class AgriAutoInfoVo {
/** 设备IMEI */
private String imei;
/** 大棚名称 */
private String agriName;
/** 工作模式 */
private Integer workMode;
/** 逻辑删除(0-未删1-已删) */
private Integer isDeleted;
/** 卷膜标识 */
private String roller;
/** 参考温度 */
private String refTempCode;
/** 参考温度 */
private String refTemp;
/** 预留风口长度(cm) */
private BigDecimal reservedLen;
/** 手动计算风口总长(cm) */
private BigDecimal manualTotalLen;
/** 自动计算风口总长(cm) */
private BigDecimal autoTotalLen;
private BigDecimal ventTotalLen;
}

View File

@ -0,0 +1,66 @@
package com.agri.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class AgriInfoView {
/** 主键ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/** 设备IMEI */
private String imei;
/** 大棚名称 */
private String agriName;
/** 工作模式 */
private Integer workMode;
/** 设备状态(0-离线1-在线2-故障) */
private Integer deviceStatus;
/** 关联用户ID */
private Long userId;
/** ts转换后的正常时间(由服务端转换入库) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date time;
@JsonSerialize(using = ToStringSerializer.class)
private Long ts;
/** 温度1(℃) */
private BigDecimal temp1;
/** 温度2(℃) */
private BigDecimal temp2;
/** 温度3(℃) */
private BigDecimal temp3;
/** 温度4(℃) */
private BigDecimal temp4;
/**
*
*/
private Integer quiltNum;
/**
*
*/
private Integer filmNum;
/**
*
*/
private Integer blindNum;
}

View File

@ -0,0 +1,18 @@
package com.agri.system.domain.vo;
import com.agri.system.domain.SysAutoTerm;
import com.agri.system.domain.SysRollerParam;
import lombok.Data;
import java.util.List;
/**
* vo
*/
@Data
public class AgriTermVo {
private SysRollerParam config;
private List<SysAutoTerm> terms;
}

View File

@ -0,0 +1,32 @@
package com.agri.system.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* @ClassName RollerTermVO
* @Description TODO
* @Author lld
* @Date 2026/3/6 1:13
* @Version 1.0
*/
// 用于承接rollerParam+autoTerms关联查询的结果
@Data
public class RollerTermVO {
// rollerParam字段
private String imei;
private String roller;
private String refTempCode;
private BigDecimal reservedLen;
private BigDecimal manualTotalLen;
private BigDecimal autoTotalLen;
private BigDecimal ventTotalLen;
// autoTerms字段一对多允许为null
private BigDecimal temp;
private BigDecimal vent;
private LocalDateTime startTime;
private LocalDateTime endTime;
}

View File

@ -0,0 +1,42 @@
package com.agri.system.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class UChartsDataView {
// X轴分类时间
private List<String> categories;
// 系列数据(温度/湿度)
private List<UChartsSeries> series;
@Data
// 内部类:单条系列数据
public static class UChartsSeries {
private String name; // 系列名称(如"温度1"
private Boolean show; // 系列名称(如"温度1"
private BigDecimal index;
private List<BigDecimal> data; // 系列数值
public UChartsSeries() {
}
public UChartsSeries(String name, Boolean show, BigDecimal index, List<BigDecimal> data) {
this.name = name;
this.show = show;
this.index = index;
this.data = data;
}
}
public UChartsDataView() {
}
public UChartsDataView(List<String> categories, List<UChartsSeries> series) {
this.categories = categories;
this.series = series;
}
}

View File

@ -1,21 +1,23 @@
package com.agri.system.mapper; package com.agri.system.mapper;
import com.agri.system.domain.SysAgriInfo; import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.vo.AgriAutoInfoVo;
import com.agri.system.domain.vo.AgriInfoView;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Mapper * Mapper
* *
* @author agri * @author agri
* @date 2026-01-08 * @date 2026-01-08
*/ */
public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo> public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo> {
{
/** /**
* *
* *
* @param id * @param id
* @return * @return
*/ */
@ -23,7 +25,7 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/** /**
* *
* *
* @param sysAgriInfo * @param sysAgriInfo
* @return * @return
*/ */
@ -31,7 +33,7 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/** /**
* *
* *
* @param sysAgriInfo * @param sysAgriInfo
* @return * @return
*/ */
@ -39,7 +41,7 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/** /**
* *
* *
* @param sysAgriInfo * @param sysAgriInfo
* @return * @return
*/ */
@ -47,7 +49,7 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/** /**
* *
* *
* @param id * @param id
* @return * @return
*/ */
@ -55,9 +57,28 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/** /**
* *
* *
* @param ids * @param ids
* @return * @return
*/ */
public int deleteSysAgriInfoByIds(Long[] ids); public int deleteSysAgriInfoByIds(Long[] ids);
/**
*
* @param sysAgriInfo
* @return
*/
public List<SysAgriInfo> findAgriByUser(SysAgriInfo sysAgriInfo);
public List<AgriInfoView> findAgriInfoByUser(SysAgriInfo sysAgriInfo);
List<AgriAutoInfoVo> findAgriOfAutoInfo();
/**
* + shared_count
* @param sysAgriInfo
* @return
*/
List<SysAgriInfo> selectShareList(SysAgriInfo sysAgriInfo);
} }

View File

@ -0,0 +1,62 @@
package com.agri.system.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.agri.system.domain.SysAgriLimitBak;
/**
* Mapper
*
* @author lld
* @date 2026-03-17
*/
public interface SysAgriLimitBakMapper extends BaseMapper<SysAgriLimitBak>
{
/**
*
*
* @param id
* @return
*/
public SysAgriLimitBak selectSysAgriLimitBakById(String id);
/**
*
*
* @param sysAgriLimitBak
* @return
*/
public List<SysAgriLimitBak> selectSysAgriLimitBakList(SysAgriLimitBak sysAgriLimitBak);
/**
*
*
* @param sysAgriLimitBak
* @return
*/
public int insertSysAgriLimitBak(SysAgriLimitBak sysAgriLimitBak);
/**
*
*
* @param sysAgriLimitBak
* @return
*/
public int updateSysAgriLimitBak(SysAgriLimitBak sysAgriLimitBak);
/**
*
*
* @param id
* @return
*/
public int deleteSysAgriLimitBakById(String id);
/**
*
*
* @param ids
* @return
*/
public int deleteSysAgriLimitBakByIds(String[] ids);
}

View File

@ -0,0 +1,62 @@
package com.agri.system.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.agri.system.domain.SysAutoTerm;
/**
* Mapper
*
* @author lld
* @date 2026-02-27
*/
public interface SysAutoTermMapper extends BaseMapper<SysAutoTerm>
{
/**
*
*
* @param id
* @return
*/
public SysAutoTerm selectSysAutoTermById(String id);
/**
*
*
* @param sysAutoTerm
* @return
*/
public List<SysAutoTerm> selectSysAutoTermList(SysAutoTerm sysAutoTerm);
/**
*
*
* @param sysAutoTerm
* @return
*/
public int insertSysAutoTerm(SysAutoTerm sysAutoTerm);
/**
*
*
* @param sysAutoTerm
* @return
*/
public int updateSysAutoTerm(SysAutoTerm sysAutoTerm);
/**
*
*
* @param id
* @return
*/
public int deleteSysAutoTermById(String id);
/**
*
*
* @param ids
* @return
*/
public int deleteSysAutoTermByIds(String[] ids);
}

View File

@ -0,0 +1,62 @@
package com.agri.system.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.agri.system.domain.SysDevOperLog;
/**
* Mapper
*
* @author lld
* @date 2026-01-29
*/
public interface SysDevOperLogMapper extends BaseMapper<SysDevOperLog>
{
/**
*
*
* @param id
* @return
*/
public SysDevOperLog selectSysDevOperLogById(Long id);
/**
*
*
* @param sysDevOperLog
* @return
*/
public List<SysDevOperLog> selectSysDevOperLogList(SysDevOperLog sysDevOperLog);
/**
*
*
* @param sysDevOperLog
* @return
*/
public int insertSysDevOperLog(SysDevOperLog sysDevOperLog);
/**
*
*
* @param sysDevOperLog
* @return
*/
public int updateSysDevOperLog(SysDevOperLog sysDevOperLog);
/**
*
*
* @param id
* @return
*/
public int deleteSysDevOperLogById(Long id);
/**
*
*
* @param ids
* @return
*/
public int deleteSysDevOperLogByIds(Long[] ids);
}

View File

@ -1,7 +1,13 @@
package com.agri.system.mapper; package com.agri.system.mapper;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map;
import com.agri.system.domain.SysAgriAlarmRelation;
import com.agri.system.domain.SysDtuData; import com.agri.system.domain.SysDtuData;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/** /**
* DTU湿Mapper * DTU湿Mapper
@ -9,7 +15,7 @@ import com.agri.system.domain.SysDtuData;
* @author agri * @author agri
* @date 2025-12-23 * @date 2025-12-23
*/ */
public interface SysDtuDataMapper public interface SysDtuDataMapper extends BaseMapper<SysDtuData>
{ {
/** /**
* DTU湿 * DTU湿
@ -60,4 +66,14 @@ public interface SysDtuDataMapper
* @return * @return
*/ */
public int deleteSysDtuDataByIds(Long[] ids); public int deleteSysDtuDataByIds(Long[] ids);
List<Map<String, Object>> getLastDtuDataByImeiList(@Param("imeiList") List<String> imeiList);
List<SysDtuData> getHistoryData(
@Param("imei") String imei,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime
);
} }

View File

@ -0,0 +1,62 @@
package com.agri.system.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.agri.system.domain.SysDtuRemarkBak;
/**
* dtuMapper
*
* @author lld
* @date 2026-03-17
*/
public interface SysDtuRemarkBakMapper extends BaseMapper<SysDtuRemarkBak>
{
/**
* dtu
*
* @param id dtu
* @return dtu
*/
public SysDtuRemarkBak selectSysDtuRemarkBakById(String id);
/**
* dtu
*
* @param sysDtuRemarkBak dtu
* @return dtu
*/
public List<SysDtuRemarkBak> selectSysDtuRemarkBakList(SysDtuRemarkBak sysDtuRemarkBak);
/**
* dtu
*
* @param sysDtuRemarkBak dtu
* @return
*/
public int insertSysDtuRemarkBak(SysDtuRemarkBak sysDtuRemarkBak);
/**
* dtu
*
* @param sysDtuRemarkBak dtu
* @return
*/
public int updateSysDtuRemarkBak(SysDtuRemarkBak sysDtuRemarkBak);
/**
* dtu
*
* @param id dtu
* @return
*/
public int deleteSysDtuRemarkBakById(String id);
/**
* dtu
*
* @param ids
* @return
*/
public int deleteSysDtuRemarkBakByIds(String[] ids);
}

View File

@ -0,0 +1,62 @@
package com.agri.system.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.agri.system.domain.SysImeiAutoLog;
/**
* Mapper
*
* @author lld
* @date 2026-03-29
*/
public interface SysImeiAutoLogMapper extends BaseMapper<SysImeiAutoLog>
{
/**
*
*
* @param id
* @return
*/
public SysImeiAutoLog selectSysImeiAutoLogById(Long id);
/**
*
*
* @param sysImeiAutoLog
* @return
*/
public List<SysImeiAutoLog> selectSysImeiAutoLogList(SysImeiAutoLog sysImeiAutoLog);
/**
*
*
* @param sysImeiAutoLog
* @return
*/
public int insertSysImeiAutoLog(SysImeiAutoLog sysImeiAutoLog);
/**
*
*
* @param sysImeiAutoLog
* @return
*/
public int updateSysImeiAutoLog(SysImeiAutoLog sysImeiAutoLog);
/**
*
*
* @param id
* @return
*/
public int deleteSysImeiAutoLogById(Long id);
/**
*
*
* @param ids
* @return
*/
public int deleteSysImeiAutoLogByIds(Long[] ids);
}

View File

@ -0,0 +1,71 @@
package com.agri.system.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.agri.system.domain.SysMessage;
/**
* Mapper
*
* @author lld
* @date 2026-03-26
*/
public interface SysMessageMapper extends BaseMapper<SysMessage>
{
/**
*
*
* @param id
* @return
*/
public SysMessage selectSysMessageById(Long id);
/**
*
*
* @param sysMessage
* @return
*/
public List<SysMessage> selectSysMessageList(SysMessage sysMessage);
/**
*
*
* @param sysMessage
* @return
*/
public int insertSysMessage(SysMessage sysMessage);
/**
*
*
* @param sysMessage
* @return
*/
public int updateSysMessage(SysMessage sysMessage);
/**
*
*
* @param id
* @return
*/
public int deleteSysMessageById(Long id);
/**
*
*
* @param ids
* @return
*/
public int deleteSysMessageByIds(Long[] ids);
/**
*
* @param sysMessage
* @return
*/
List<SysMessage> getMsgOverview(SysMessage sysMessage);
}

View File

@ -0,0 +1,62 @@
package com.agri.system.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.agri.system.domain.SysRollerAir;
/**
* Mapper
*
* @author lld
* @date 2026-03-04
*/
public interface SysRollerAirMapper extends BaseMapper<SysRollerAir>
{
/**
*
*
* @param id
* @return
*/
public SysRollerAir selectSysRollerAirById(Long id);
/**
*
*
* @param sysRollerAir
* @return
*/
public List<SysRollerAir> selectSysRollerAirList(SysRollerAir sysRollerAir);
/**
*
*
* @param sysRollerAir
* @return
*/
public int insertSysRollerAir(SysRollerAir sysRollerAir);
/**
*
*
* @param sysRollerAir
* @return
*/
public int updateSysRollerAir(SysRollerAir sysRollerAir);
/**
*
*
* @param id
* @return
*/
public int deleteSysRollerAirById(Long id);
/**
*
*
* @param ids
* @return
*/
public int deleteSysRollerAirByIds(Long[] ids);
}

View File

@ -0,0 +1,68 @@
package com.agri.system.mapper;
import com.agri.system.domain.SysRollerParam;
import com.agri.system.domain.vo.RollerTermVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* Mapper
*
* @author lld
* @date 2026-02-27
*/
public interface SysRollerParamMapper extends BaseMapper<SysRollerParam>
{
/**
*
*
* @param id
* @return
*/
public SysRollerParam selectSysRollerParamById(String id);
/**
*
*
* @param sysRollerParam
* @return
*/
public List<SysRollerParam> selectSysRollerParamList(SysRollerParam sysRollerParam);
/**
*
*
* @param sysRollerParam
* @return
*/
public int insertSysRollerParam(SysRollerParam sysRollerParam);
/**
*
*
* @param sysRollerParam
* @return
*/
public int updateSysRollerParam(SysRollerParam sysRollerParam);
/**
*
*
* @param id
* @return
*/
public int deleteSysRollerParamById(String id);
/**
*
*
* @param ids
* @return
*/
public int deleteSysRollerParamByIds(String[] ids);
List<RollerTermVO> getRollerTerms(@Param("imeiList") List<String> imeiList);
}

View File

@ -0,0 +1,81 @@
package com.agri.system.mapper;
import com.agri.common.core.domain.entity.SysUser;
import com.agri.system.domain.SysUserAgri;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
import java.util.Map;
/**
* -Mapper
*
* @author lld
* @date 2026-01-25
*/
public interface SysUserAgriMapper extends BaseMapper<SysUserAgri>
{
/**
* -
*
* @param id -
* @return -
*/
public SysUserAgri selectSysUserAgriById(Long id);
/**
* -
*
* @param sysUserAgri -
* @return -
*/
public List<SysUserAgri> selectSysUserAgriList(SysUserAgri sysUserAgri);
/**
* -
*
* @param sysUserAgri -
* @return
*/
public int insertSysUserAgri(SysUserAgri sysUserAgri);
/**
* -
*
* @param sysUserAgri -
* @return
*/
public int updateSysUserAgri(SysUserAgri sysUserAgri);
/**
* -
*
* @param id -
* @return
*/
public int deleteSysUserAgriById(Long id);
/**
* -
*
* @param ids
* @return
*/
public int deleteSysUserAgriByIds(Long[] ids);
/**
*
* @param sysUserAgri
* @return
*/
List<SysUserAgri> findAgriUser(SysUserAgri sysUserAgri);
/**
* disbaledtrue
* @param sysUserAgri
* @return
*/
List<SysUserAgri> findAllUser(SysUserAgri sysUserAgri);
List<Map<String, Object>> getRecentShareUser(SysUserAgri sysUserAgri);
}

View File

@ -1,16 +1,18 @@
package com.agri.system.mapper; package com.agri.system.mapper;
import com.agri.common.core.domain.entity.SysUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.agri.common.core.domain.entity.SysUser;
/** /**
* *
* *
* @author ruoyi * @author ruoyi
*/ */
public interface SysUserMapper public interface SysUserMapper extends BaseMapper<SysUser>
{ {
/** /**
* *

View File

@ -0,0 +1,15 @@
package com.agri.system.service;
import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.SysMessage;
import java.util.List;
import java.util.Map;
public interface AgriService {
List<SysMessage> saveMessage(List<SysAgriInfo> offlineList);
List<String> queryAllGreenhouseImei(List<SysAgriInfo> agriInfos);
}

View File

@ -1,20 +1,23 @@
package com.agri.system.service; package com.agri.system.service;
import com.agri.system.domain.SysAgriInfo; import com.agri.system.domain.SysAgriInfo;
import com.agri.system.domain.vo.AgriAutoInfoVo;
import com.agri.system.domain.vo.AgriInfoView;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Service * Service
* *
* @author agri * @author agri
* @date 2026-01-08 * @date 2026-01-08
*/ */
public interface ISysAgriInfoService extends IService<SysAgriInfo> { public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/** /**
* *
* *
* @param id * @param id
* @return * @return
*/ */
@ -22,7 +25,7 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/** /**
* *
* *
* @param sysAgriInfo * @param sysAgriInfo
* @return * @return
*/ */
@ -30,7 +33,7 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/** /**
* *
* *
* @param sysAgriInfo * @param sysAgriInfo
* @return * @return
*/ */
@ -38,7 +41,7 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/** /**
* *
* *
* @param sysAgriInfo * @param sysAgriInfo
* @return * @return
*/ */
@ -46,7 +49,7 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/** /**
* *
* *
* @param ids * @param ids
* @return * @return
*/ */
@ -54,9 +57,27 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/** /**
* *
* *
* @param id * @param id
* @return * @return
*/ */
public int deleteSysAgriInfoById(Long id); public int deleteSysAgriInfoById(Long id);
public List<SysAgriInfo> findAgriByUser(SysAgriInfo sysAgriInfo);
public List<AgriInfoView> findAgriInfoByUser(SysAgriInfo sysAgriInfo);
Map<String,Object> addAgriFromMobile(SysAgriInfo sysAgriInfo);
List<AgriAutoInfoVo> findAgriOfAutoInfo();
List<String> queryImeiByUserId(Long userId);
/**
* +
* @return
*/
Map<String, Object> selectShareInfoByUser(SysAgriInfo agriInfo);
} }

View File

@ -0,0 +1,65 @@
package com.agri.system.service;
import java.util.List;
import com.baomidou.mybatisplus.extension.service.IService;
import com.agri.system.domain.SysAgriLimitBak;
/**
* Service
*
* @author lld
* @date 2026-03-17
*/
public interface ISysAgriLimitBakService extends IService<SysAgriLimitBak> {
/**
*
*
* @param id
* @return
*/
public SysAgriLimitBak selectSysAgriLimitBakById(String id);
/**
*
*
* @param sysAgriLimitBak
* @return
*/
public List<SysAgriLimitBak> selectSysAgriLimitBakList(SysAgriLimitBak sysAgriLimitBak);
/**
*
*
* @param sysAgriLimitBak
* @return
*/
public int insertSysAgriLimitBak(SysAgriLimitBak sysAgriLimitBak);
/**
*
*
* @param sysAgriLimitBak
* @return
*/
public int updateSysAgriLimitBak(SysAgriLimitBak sysAgriLimitBak);
/**
*
*
* @param ids
* @return
*/
public int deleteSysAgriLimitBakByIds(String[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteSysAgriLimitBakById(String id);
SysAgriLimitBak getAgriLimitByImei(String imei);
boolean saveAgriLimit(SysAgriLimitBak sysAgriLimitBak);
}

View File

@ -0,0 +1,71 @@
package com.agri.system.service;
import java.util.List;
import com.agri.system.domain.vo.AgriTermVo;
import com.baomidou.mybatisplus.extension.service.IService;
import com.agri.system.domain.SysAutoTerm;
/**
* Service
*
* @author lld
* @date 2026-02-27
*/
public interface ISysAutoTermService extends IService<SysAutoTerm> {
/**
*
*
* @param id
* @return
*/
public SysAutoTerm selectSysAutoTermById(String id);
/**
*
*
* @param sysAutoTerm
* @return
*/
public List<SysAutoTerm> selectSysAutoTermList(SysAutoTerm sysAutoTerm);
/**
*
*
* @param sysAutoTerm
* @return
*/
public int insertSysAutoTerm(SysAutoTerm sysAutoTerm);
/**
*
*
* @param sysAutoTerm
* @return
*/
public int updateSysAutoTerm(SysAutoTerm sysAutoTerm);
/**
*
*
* @param ids
* @return
*/
public int deleteSysAutoTermByIds(String[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteSysAutoTermById(String id);
boolean saveAgriTerm(List<AgriTermVo> agriTerms);
String validate(List<AgriTermVo> agriTerms);
List<AgriTermVo> getAgriTerm(String imei);
}

View File

@ -0,0 +1,66 @@
package com.agri.system.service;
import java.util.List;
import java.util.Map;
import com.baomidou.mybatisplus.extension.service.IService;
import com.agri.system.domain.SysDevOperLog;
/**
* Service
*
* @author lld
* @date 2026-01-29
*/
public interface ISysDevOperLogService extends IService<SysDevOperLog> {
/**
*
*
* @param id
* @return
*/
public SysDevOperLog selectSysDevOperLogById(Long id);
/**
*
*
* @param sysDevOperLog
* @return
*/
public List<SysDevOperLog> selectSysDevOperLogList(SysDevOperLog sysDevOperLog);
/**
*
*
* @param sysDevOperLog
* @return
*/
public int insertSysDevOperLog(SysDevOperLog sysDevOperLog);
/**
*
*
* @param sysDevOperLog
* @return
*/
public int updateSysDevOperLog(SysDevOperLog sysDevOperLog);
/**
*
*
* @param ids
* @return
*/
public int deleteSysDevOperLogByIds(Long[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteSysDevOperLogById(Long id);
Map<String, Map<String, Integer>> getTodayLogCountByImeiMap(List<String> imeiList);
}

View File

@ -1,19 +1,24 @@
package com.agri.system.service; package com.agri.system.service;
import java.util.List;
import com.agri.system.domain.SysDtuData; import com.agri.system.domain.SysDtuData;
import com.agri.system.domain.vo.UChartsDataView;
import com.baomidou.mybatisplus.extension.service.IService;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/** /**
* DTU湿Service * DTU湿Service
* *
* @author agri * @author agri
* @date 2025-12-23 * @date 2025-12-23
*/ */
public interface ISysDtuDataService public interface ISysDtuDataService extends IService<SysDtuData>
{ {
/** /**
* DTU湿 * DTU湿
* *
* @param id DTU湿 * @param id DTU湿
* @return DTU湿 * @return DTU湿
*/ */
@ -21,7 +26,7 @@ public interface ISysDtuDataService
/** /**
* DTU湿 * DTU湿
* *
* @param sysDtuData DTU湿 * @param sysDtuData DTU湿
* @return DTU湿 * @return DTU湿
*/ */
@ -36,7 +41,7 @@ public interface ISysDtuDataService
/** /**
* DTU湿 * DTU湿
* *
* @param sysDtuData DTU湿 * @param sysDtuData DTU湿
* @return * @return
*/ */
@ -44,7 +49,7 @@ public interface ISysDtuDataService
/** /**
* DTU湿 * DTU湿
* *
* @param sysDtuData DTU湿 * @param sysDtuData DTU湿
* @return * @return
*/ */
@ -52,7 +57,7 @@ public interface ISysDtuDataService
/** /**
* DTU湿 * DTU湿
* *
* @param ids DTU湿 * @param ids DTU湿
* @return * @return
*/ */
@ -60,9 +65,15 @@ public interface ISysDtuDataService
/** /**
* DTU湿 * DTU湿
* *
* @param id DTU湿 * @param id DTU湿
* @return * @return
*/ */
public int deleteSysDtuDataById(Long id); public int deleteSysDtuDataById(Long id);
List<Map<String, Object>> getLastDtuDataByImeiList(List<String> imeiList);
UChartsDataView getHistoryData(String imei, LocalDateTime startTime, LocalDateTime endTime);
} }

View File

@ -0,0 +1,70 @@
package com.agri.system.service;
import java.util.List;
import com.agri.common.core.domain.AjaxResult;
import com.baomidou.mybatisplus.extension.service.IService;
import com.agri.system.domain.SysDtuRemarkBak;
import org.springframework.web.bind.annotation.RequestParam;
/**
* dtuService
*
* @author lld
* @date 2026-03-17
*/
public interface ISysDtuRemarkBakService extends IService<SysDtuRemarkBak> {
/**
* dtu
*
* @param id dtu
* @return dtu
*/
public SysDtuRemarkBak selectSysDtuRemarkBakById(String id);
/**
* dtu
*
* @param sysDtuRemarkBak dtu
* @return dtu
*/
public List<SysDtuRemarkBak> selectSysDtuRemarkBakList(SysDtuRemarkBak sysDtuRemarkBak);
/**
* dtu
*
* @param sysDtuRemarkBak dtu
* @return
*/
public int insertSysDtuRemarkBak(SysDtuRemarkBak sysDtuRemarkBak);
/**
* dtu
*
* @param sysDtuRemarkBak dtu
* @return
*/
public int updateSysDtuRemarkBak(SysDtuRemarkBak sysDtuRemarkBak);
/**
* dtu
*
* @param ids dtu
* @return
*/
public int deleteSysDtuRemarkBakByIds(String[] ids);
/**
* dtu
*
* @param id dtu
* @return
*/
public int deleteSysDtuRemarkBakById(String id);
SysDtuRemarkBak getDtuRemarkByImei(String imei);
boolean saveAgriRemark(SysDtuRemarkBak sysDtuRemarkBak);
}

View File

@ -0,0 +1,61 @@
package com.agri.system.service;
import java.util.List;
import com.baomidou.mybatisplus.extension.service.IService;
import com.agri.system.domain.SysImeiAutoLog;
/**
* Service
*
* @author lld
* @date 2026-03-29
*/
public interface ISysImeiAutoLogService extends IService<SysImeiAutoLog> {
/**
*
*
* @param id
* @return
*/
public SysImeiAutoLog selectSysImeiAutoLogById(Long id);
/**
*
*
* @param sysImeiAutoLog
* @return
*/
public List<SysImeiAutoLog> selectSysImeiAutoLogList(SysImeiAutoLog sysImeiAutoLog);
/**
*
*
* @param sysImeiAutoLog
* @return
*/
public int insertSysImeiAutoLog(SysImeiAutoLog sysImeiAutoLog);
/**
*
*
* @param sysImeiAutoLog
* @return
*/
public int updateSysImeiAutoLog(SysImeiAutoLog sysImeiAutoLog);
/**
*
*
* @param ids
* @return
*/
public int deleteSysImeiAutoLogByIds(Long[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteSysImeiAutoLogById(Long id);
}

View File

@ -0,0 +1,63 @@
package com.agri.system.service;
import java.util.List;
import com.baomidou.mybatisplus.extension.service.IService;
import com.agri.system.domain.SysMessage;
/**
* Service
*
* @author lld
* @date 2026-03-26
*/
public interface ISysMessageService extends IService<SysMessage> {
/**
*
*
* @param id
* @return
*/
public SysMessage selectSysMessageById(Long id);
/**
*
*
* @param sysMessage
* @return
*/
public List<SysMessage> selectSysMessageList(SysMessage sysMessage);
/**
*
*
* @param sysMessage
* @return
*/
public int insertSysMessage(SysMessage sysMessage);
/**
*
*
* @param sysMessage
* @return
*/
public int updateSysMessage(SysMessage sysMessage);
/**
*
*
* @param ids
* @return
*/
public int deleteSysMessageByIds(Long[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteSysMessageById(Long id);
List<SysMessage> getMsgOverview(SysMessage sysMessage);
}

View File

@ -0,0 +1,61 @@
package com.agri.system.service;
import java.util.List;
import com.baomidou.mybatisplus.extension.service.IService;
import com.agri.system.domain.SysRollerAir;
/**
* Service
*
* @author lld
* @date 2026-03-04
*/
public interface ISysRollerAirService extends IService<SysRollerAir> {
/**
*
*
* @param id
* @return
*/
public SysRollerAir selectSysRollerAirById(Long id);
/**
*
*
* @param sysRollerAir
* @return
*/
public List<SysRollerAir> selectSysRollerAirList(SysRollerAir sysRollerAir);
/**
*
*
* @param sysRollerAir
* @return
*/
public int insertSysRollerAir(SysRollerAir sysRollerAir);
/**
*
*
* @param sysRollerAir
* @return
*/
public int updateSysRollerAir(SysRollerAir sysRollerAir);
/**
*
*
* @param ids
* @return
*/
public int deleteSysRollerAirByIds(Long[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteSysRollerAirById(Long id);
}

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