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
# -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
# EXPOSE 8088 5005
# -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">
<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>
# MQTT 压测脚本说明
## 平台简介
## 脚本列表
- 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。
* 权限认证使用Jwt支持多终端认证系统。
* 支持加载动态权限菜单,多方式轻松权限控制。
* 高效率开发,使用代码生成器可以一键生成前后端代码。
* 提供了技术栈([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) 点击按钮入群。
## 停止压测
```bash
pkill mosquitto_pub

View File

@ -3,16 +3,15 @@ package com.agri.web.controller.mqtt;
import com.agri.common.annotation.Log;
import com.agri.common.core.domain.AjaxResult;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@ -27,7 +26,15 @@ public class MqttController {
private static final Logger log = LoggerFactory.getLogger(MqttController.class);
@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)
public String subscribe(@RequestParam String clientId, @RequestParam String deviceId) {
try {
mqttMessageHandler.subscribeDevice(clientId, deviceId);
mqttSubscriptionManager.subscribeDevice(clientId, deviceId);
return "订阅成功";
} catch (IllegalArgumentException e) {
log.error("MQTT单个订阅失败{}", e.getMessage());
@ -53,7 +60,7 @@ public class MqttController {
@DeleteMapping("/single")
public String unsubscribe(@RequestParam String clientId, @RequestParam String deviceId) {
try {
mqttMessageHandler.unsubscribeDevice(clientId, deviceId);
mqttSubscriptionManager.unsubscribeDevice(clientId, deviceId);
return "取消订阅成功";
} catch (IllegalArgumentException e) {
log.error("MQTT单个取消订阅失败{}", e.getMessage());
@ -72,7 +79,7 @@ public class MqttController {
public AjaxResult subscribeAll(@RequestParam String clientId) {
try {
// 返回前端需要取消的MQTT主题列表
return AjaxResult.success(mqttMessageHandler.subscribeAllDeviceByUserId(clientId));
return AjaxResult.success(mqttSubscriptionManager.subscribeAllDeviceByUserId(clientId));
} catch (IllegalArgumentException e) {
log.error("MQTT批量订阅失败{}", e.getMessage());
// 异常时返回空列表,避免前端解析失败
@ -91,7 +98,7 @@ public class MqttController {
public List<String> unsubscribeAll(@RequestParam String clientId) {
try {
// 返回前端需要取消的MQTT主题列表
return mqttMessageHandler.unsubscribeAllDevice(clientId);
return mqttSubscriptionManager.unsubscribeAllDevice(clientId);
} catch (IllegalArgumentException e) {
log.error("MQTT批量取消订阅失败{}", e.getMessage());
// 异常时返回空列表,避免前端解析失败
@ -109,7 +116,7 @@ public class MqttController {
@Log(title = "手动触发MQTT重连", businessType = BusinessType.OTHER)
public String manualReconnect() {
try {
return mqttMessageHandler.manualReconnect();
return mqttClientManager.manualReconnect();
} catch (Exception e) {
log.error("MQTT手动重连异常", e);
return "手动重连失败:" + e.getMessage();
@ -121,13 +128,32 @@ public class MqttController {
* 便
*/
@GetMapping("/status")
@Log(title = "手动触发MQTT重连", businessType = BusinessType.SELECT)
public String getMqttStatus() {
public AjaxResult getMqttStatus() {
try {
return mqttMessageHandler.getMqttStatus();
return AjaxResult.success(mqttClientManager.getMqttStatus());
} catch (Exception 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;
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.utils.StringUtils;
import com.agri.framework.config.MqttConfig;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
@ -11,6 +13,7 @@ import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
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.GetMapping;
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.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@ -37,6 +41,9 @@ import java.util.Map;
@RequestMapping("/test/user")
public class TestController extends BaseController
{
@Resource
private MqttConfig.MqttMessageSender mqttMessageSender;
private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();
{
users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
@ -118,6 +125,16 @@ public class TestController extends BaseController
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 = "用户实体")

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:
# 主库数据源
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
password: lld123
# 从库数据源

View File

@ -1,15 +1,17 @@
spring:
# MQTT配置
mqtt:
host: tcp://122.51.109.52:1883 # 设备/后端的MQTT TCP地址
host: tcp://mq.xiaoces.com:1883 # 设备/后端的MQTT TCP地址
username: admin # Mosquitto共用账号
password: Admin#12345678 # Mosquitto密码
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 # 消息可靠性
timeout: 60 # 连接超时
keep-alive: 60 # 心跳间隔
latest-ttl-seconds: 120 #设备最新状态缓存的过期时间(秒)。
# 自动关闭任务线程池大小
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
# 版权年份
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/agri/uploadPathLinux配置 /home/agri/uploadPath
profile: D:/agri/uploadPath
# 文件路径 示例( Windows配置D:/agri/uploadPathLinux配置 /opt/agri/uploadPath
profile: /opt/agri/uploadPath
# 获取ip地址开关
addressEnabled: true
# 验证码类型 math 数字计算 char 字符验证
captchaType: math
# 卷膜滚轴长度和秒数
per-lap:
sec: 18 # 以秒为单位
len: 2.13 # 以cm为单位
# 开启增强
knife4j:
enable: true
@ -40,12 +44,6 @@ server:
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 日志配置
logging:
level:
com.agri: debug
org.springframework: warn
# 用户配置
user:
password:
@ -61,7 +59,7 @@ spring:
# 国际化资源文件路径
basename: i18n/messages
profiles:
active: druid,mqtt
active: druid,mqtt,dev
# 文件上传
servlet:
multipart:
@ -79,7 +77,7 @@ spring:
# redis 配置
redis:
# 地址
host: 122.51.109.52
host: 49.233.181.63
# 端口默认为6379
port: 6379
# 数据库索引

View File

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

View File

@ -1,7 +1,9 @@
package com.agri.common.constant;
import java.math.BigDecimal;
import java.util.Locale;
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",
"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;
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.ColumnType;
import com.agri.common.annotation.Excel.Type;
import com.agri.common.annotation.Excels;
import com.agri.common.annotation.Sensitive;
import com.agri.common.core.domain.BaseEntity;
import com.agri.common.enums.DesensitizedType;
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
*
*
* @author ruoyi
*/
@TableName("sys_user")
public class SysUser extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 用户ID */
@Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号")
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
/** 部门ID */
@Excel(name = "部门编号", type = Type.IMPORT)
private Long deptId;
/** 用户账号 */
@Excel(name = "clientId")
private String clientId;
/** 用户账号 */
@Excel(name = "登录名称")
private String userName;
@ -79,18 +97,23 @@ public class SysUser extends BaseEntity
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
})
@TableField(exist = false)
private SysDept dept;
/** 角色对象 */
@TableField(exist = false)
private List<SysRole> roles;
/** 角色组 */
@TableField(exist = false)
private Long[] roleIds;
/** 岗位组 */
@TableField(exist = false)
private Long[] postIds;
/** 角色ID */
@TableField(exist = false)
private Long roleId;
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)
{
this.userId = userId;
@ -317,6 +349,7 @@ public class SysUser extends BaseEntity
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("userId", getUserId())
.append("deptId", getDeptId())
.append("clientId", getClientId())
.append("userName", getUserName())
.append("nickName", getNickName())
.append("email", getEmail())

View File

@ -16,6 +16,7 @@ public class LoginBody
*
*/
private String password;
private String phonenumber;
/**
*
@ -66,4 +67,12 @@ public class LoginBody
{
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;
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.HttpStatus;
import com.agri.common.core.domain.entity.SysRole;
import com.agri.common.core.domain.model.LoginUser;
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
{
private static final Logger log = LoggerFactory.getLogger(SecurityUtils.class);
/**
* ID
**/
@ -28,7 +33,11 @@ public class SecurityUtils
{
try
{
return getLoginUser().getUserId();
LoginUser loginUser = getLoginUser();
if (loginUser!= null) {
return loginUser.getUserId();
}
return null;
}
catch (Exception e)
{
@ -58,7 +67,11 @@ public class SecurityUtils
{
try
{
return getLoginUser().getUsername();
LoginUser loginUser = getLoginUser();
if (loginUser !=null ) {
return loginUser.getUsername();
}
return null;
}
catch (Exception e)
{
@ -71,14 +84,18 @@ public class SecurityUtils
**/
public static LoginUser getLoginUser()
{
try
{
return (LoginUser) getAuthentication().getPrincipal();
}
catch (Exception e)
{
throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
try {
Authentication authentication = getAuthentication();
if (authentication == null || !(authentication.getPrincipal() instanceof LoginUser)) {
return null; // 无用户上下文时返回 null
}
return (LoginUser) authentication.getPrincipal();
} catch (Exception e) {
log.error("获取用户信息异常: {}", HttpStatus.UNAUTHORIZED);
}
return null;
}
/**
@ -114,6 +131,15 @@ public class SecurityUtils
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;
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.IOException;
import java.io.InputStream;
@ -11,17 +28,6 @@ import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
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
@ -32,6 +38,69 @@ public class HttpUtils
{
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
*

View File

@ -19,7 +19,7 @@ public class AddressUtils
private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
// 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";

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当前时间
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
// 示例填充创建人假设从上下文获取当前用户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当前时间
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) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录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("/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.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
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)
{
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(),phonenumber = registerBody.getPhonenumber();
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
@ -56,6 +56,14 @@ public class SysRegisterService
{
msg = "用户名不能为空";
}
else if (StringUtils.isEmpty(phonenumber))
{
msg = "用户手机号不能为空";
}
else if (phonenumber.length() != 11)
{
msg = "手机号必须为11位";
}
else if (StringUtils.isEmpty(password))
{
msg = "用户密码不能为空";
@ -79,7 +87,17 @@ public class SysRegisterService
sysUser.setNickName(username);
sysUser.setPwdUpdateDate(DateUtils.getNowDate());
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);
// 默认普通角色
userService.insertUserAuth(sysUser.getUserId(), new Long[] {2L});
if (!regFlag)
{
msg = "注册失败,请联系系统管理人员";

View File

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

View File

@ -34,7 +34,15 @@
<groupId>com.agri</groupId>
<artifactId>agri-common</artifactId>
</dependency>
<dependency>
<groupId>com.agri</groupId>
<artifactId>agri-system</artifactId>
</dependency>
<dependency>
<groupId>com.agri</groupId>
<artifactId>agri-framework</artifactId>
</dependency>
</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.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.domain.vo.AgriInfoView;
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.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 org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* Controller
@ -35,6 +34,8 @@ public class SysAgriInfoController extends BaseController
@Autowired
private ISysAgriInfoService sysAgriInfoService;
@Autowired
private ISysUserAgriService userAgriService;
/**
*
*/
@ -43,10 +44,16 @@ public class SysAgriInfoController extends BaseController
public TableDataInfo list(SysAgriInfo sysAgriInfo)
{
startPage();
List<SysAgriInfo> list = sysAgriInfoService.selectSysAgriInfoList(sysAgriInfo);
List<SysAgriInfo> list = sysAgriInfoService.findAgriByUser(sysAgriInfo);
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));
}
@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;
import java.time.LocalDateTime;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.format.annotation.DateTimeFormat;
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 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;
@ -112,4 +108,14 @@ public class SysDtuDataController extends BaseController
{
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;
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.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.poi.ExcelUtil;
import com.agri.system.domain.SysDtuRemark;
import com.agri.system.service.ISysDtuRemarkService;
import com.agri.common.utils.poi.ExcelUtil;
import com.agri.common.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* dtuController
*
*
* @author lld
* @date 2026-01-21
*/
@ -57,7 +56,7 @@ public class SysDtuRemarkController extends BaseController
/**
* dtu
*/
@PreAuthorize("@ss.hasPermi('assets:remark:query')")
@PreAuthorize("@ss.hasPermi('assets:remark:list')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
@ -101,7 +100,7 @@ public class SysDtuRemarkController extends BaseController
/**
* dtu
*/
@PreAuthorize("@ss.hasPermi('assets:remark:query')")
@PreAuthorize("@ss.hasPermi('assets:remark:list')")
@GetMapping(value = "/getDtuByImei")
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.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
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 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.ToStringStyle;
@ -19,6 +24,7 @@ import java.util.Date;
* @author agri
* @date 2026-01-08
*/
@Data
@TableName("sys_agri_info")
public class SysAgriInfo extends BaseEntity
{
@ -37,198 +43,92 @@ public class SysAgriInfo extends BaseEntity
@Excel(name = "大棚名称")
private String agriName;
/** 工作模式 */
@Excel(name = "工作模式")
private Integer workMode;
/** 关联用户ID */
@Excel(name = "关联用户ID")
@JsonSerialize(using = ToStringSerializer.class)
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-开启) */
@Excel(name = "告警开关(0-关闭1-开启)")
private Integer alarmStatus;
@TableField(exist = false)
/** 设备状态(0-离线1-在线2-故障) */
@Excel(name = "设备状态(0-离线1-在线2-故障)")
private Integer deviceStatus;
/** 安装时间 */
@Getter
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "安装时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date installTime;
/** 安装位置 */
@Setter
@Excel(name = "安装位置")
private String location;
/** 逻辑删除(0-未删1-已删) */
@Excel(name = "逻辑删除(0-未删1-已删)")
@TableLogic(value = "0", delval = "1")
private Integer isDeleted;
public void setId(Long id)
{
this.id = id;
}
/**
* 01
*/
@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)
{
this.agriName = agriName;
}
/** 温度上限(℃) */
@Excel(name = "温度上限(℃)")
private BigDecimal tempUp;
public String getAgriName()
{
return agriName;
}
/** 温度下限(℃) */
@Excel(name = "温度下限(℃)")
private BigDecimal tempLow;
public void setUserId(Long userId)
{
this.userId = userId;
}
/** 湿度上限(%RH) */
@Excel(name = "湿度上限(%RH)")
private BigDecimal humiUp;
public Long getUserId()
{
return userId;
}
/** 湿度下限(%RH) */
@Excel(name = "湿度下限(%RH)")
private BigDecimal humiLow;
public void setTempUpper(BigDecimal tempUpper)
{
this.tempUpper = tempUpper;
}
@TableField(exist = false)
private String title;
public BigDecimal getTempUpper()
{
return tempUpper;
}
@TableField(exist = false)
private String msg;
public void setTempLower(BigDecimal tempLower)
{
this.tempLower = tempLower;
}
@TableField(exist = false)
private Integer status;
public BigDecimal getTempLower()
{
return tempLower;
}
/** 关联用户ID */
@TableField(exist = false)
@JsonSerialize(using = ToStringSerializer.class)
private Long inviteBy;
public void setHumiUpper(BigDecimal humiUpper)
{
this.humiUpper = humiUpper;
}
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();
}
/** 已分享次数(非数据库字段,仅用于查询返回) */
@TableField(exist = false)
private Integer sharedCount;
}

View File

@ -50,6 +50,14 @@ public class SysAgriLimit extends BaseEntity
@Excel(name = "卷被关限位时间(秒)")
private String jbgLimit;
/** 卷被开限位时间(秒) */
@Excel(name = "卷帘开限位时间(秒)")
private String jlkLimit;
/** 卷被关限位时间(秒) */
@Excel(name = "卷帘关限位时间(秒)")
private String jlgLimit;
/** 卷膜1开限位时间(秒) */
@Excel(name = "卷膜1开限位时间(秒)")
private String jm1kLimit;
@ -88,7 +96,23 @@ public class SysAgriLimit extends BaseEntity
@Excel(name = "删除时间", width = 30, dateFormat = "yyyy-MM-dd")
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;
}
@ -233,6 +257,8 @@ public class SysAgriLimit extends BaseEntity
.append("imei", getImei())
.append("jbkLimit", getJbkLimit())
.append("jbgLimit", getJbgLimit())
.append("jlkLimit", getJlkLimit())
.append("jlgLimit", getJlgLimit())
.append("jm1kLimit", getJm1kLimit())
.append("jm1gLimit", getJm1gLimit())
.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 java.math.BigDecimal;
import java.util.Date;
import java.time.LocalDateTime;
/**
* DTU湿 sys_dtu_data
*
*
* @author agri
* @date 2025-12-23
* @LocalDateTime 2025-12-23
*/
public class SysDtuData extends BaseEntity
{
@ -36,7 +37,7 @@ public class SysDtuData extends BaseEntity
/** ts转换后的正常时间(由服务端转换入库) */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Excel(name = "ts转换后的正常时间(由服务端转换入库)", width = 30, dateFormat = "yyyy-MM-dd")
private Date time;
private LocalDateTime time;
/** 温度1(℃) */
@Excel(name = "温度1(℃)")
@ -74,132 +75,132 @@ public class SysDtuData extends BaseEntity
@Excel(name = "原始JSON元信息(原始上报码/额外字段)")
private String raw;
public void setId(Long id)
public void setId(Long id)
{
this.id = id;
}
public Long getId()
public Long getId()
{
return id;
}
public void setImei(String imei)
public void setImei(String imei)
{
this.imei = imei;
}
public String getImei()
public String getImei()
{
return imei;
}
public void setTs(Long ts)
public void setTs(Long ts)
{
this.ts = ts;
}
public Long getTs()
public Long getTs()
{
return ts;
}
public void setTime(Date time)
public void setTime(LocalDateTime time)
{
this.time = time;
}
public Date getTime()
public LocalDateTime getTime()
{
return time;
}
public void setTemp1(BigDecimal temp1)
public void setTemp1(BigDecimal temp1)
{
this.temp1 = temp1;
}
public BigDecimal getTemp1()
public BigDecimal getTemp1()
{
return temp1;
}
public void setHumi1(BigDecimal humi1)
public void setHumi1(BigDecimal humi1)
{
this.humi1 = humi1;
}
public BigDecimal getHumi1()
public BigDecimal getHumi1()
{
return humi1;
}
public void setTemp2(BigDecimal temp2)
public void setTemp2(BigDecimal temp2)
{
this.temp2 = temp2;
}
public BigDecimal getTemp2()
public BigDecimal getTemp2()
{
return temp2;
}
public void setHumi2(BigDecimal humi2)
public void setHumi2(BigDecimal humi2)
{
this.humi2 = humi2;
}
public BigDecimal getHumi2()
public BigDecimal getHumi2()
{
return humi2;
}
public void setTemp3(BigDecimal temp3)
public void setTemp3(BigDecimal temp3)
{
this.temp3 = temp3;
}
public BigDecimal getTemp3()
public BigDecimal getTemp3()
{
return temp3;
}
public void setHumi3(BigDecimal humi3)
public void setHumi3(BigDecimal humi3)
{
this.humi3 = humi3;
}
public BigDecimal getHumi3()
public BigDecimal getHumi3()
{
return humi3;
}
public void setTemp4(BigDecimal temp4)
public void setTemp4(BigDecimal temp4)
{
this.temp4 = temp4;
}
public BigDecimal getTemp4()
public BigDecimal getTemp4()
{
return temp4;
}
public void setHumi4(BigDecimal humi4)
public void setHumi4(BigDecimal humi4)
{
this.humi4 = humi4;
}
public BigDecimal getHumi4()
public BigDecimal getHumi4()
{
return humi4;
}
public void setRaw(String raw)
public void setRaw(String raw)
{
this.raw = raw;
}
public String getRaw()
public String getRaw()
{
return raw;
}

View File

@ -36,6 +36,14 @@ public class SysDtuRemark extends BaseEntity
@Excel(name = "卷被关")
private String jbg;
/** 卷被开 */
@Excel(name = "卷帘开")
private String jlk;
/** 卷被关 */
@Excel(name = "卷帘关")
private String jlg;
/** 卷膜1开 */
@Excel(name = "卷膜1开")
private String jm1k;
@ -300,11 +308,29 @@ public class SysDtuRemark extends BaseEntity
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
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("imei", getImei())
.append("jlk", getJlk())
.append("jlg", getJlg())
.append("jbk", getJbk())
.append("jbg", getJbg())
.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;
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 java.util.List;
import java.util.Map;
/**
* Mapper
*
*
* @author agri
* @date 2026-01-08
*/
public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
{
public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo> {
/**
*
*
*
* @param id
* @return
*/
@ -23,7 +25,7 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/**
*
*
*
* @param sysAgriInfo
* @return
*/
@ -31,7 +33,7 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/**
*
*
*
* @param sysAgriInfo
* @return
*/
@ -39,7 +41,7 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/**
*
*
*
* @param sysAgriInfo
* @return
*/
@ -47,7 +49,7 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/**
*
*
*
* @param id
* @return
*/
@ -55,9 +57,28 @@ public interface SysAgriInfoMapper extends BaseMapper<SysAgriInfo>
/**
*
*
*
* @param ids
* @return
*/
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;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import com.agri.system.domain.SysAgriAlarmRelation;
import com.agri.system.domain.SysDtuData;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* DTU湿Mapper
@ -9,7 +15,7 @@ import com.agri.system.domain.SysDtuData;
* @author agri
* @date 2025-12-23
*/
public interface SysDtuDataMapper
public interface SysDtuDataMapper extends BaseMapper<SysDtuData>
{
/**
* DTU湿
@ -60,4 +66,14 @@ public interface SysDtuDataMapper
* @return
*/
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;
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.List;
import org.apache.ibatis.annotations.Param;
import com.agri.common.core.domain.entity.SysUser;
/**
*
*
*
* @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;
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 java.util.List;
import java.util.Map;
/**
* Service
*
*
* @author agri
* @date 2026-01-08
*/
public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/**
*
*
*
* @param id
* @return
*/
@ -22,7 +25,7 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/**
*
*
*
* @param sysAgriInfo
* @return
*/
@ -30,7 +33,7 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/**
*
*
*
* @param sysAgriInfo
* @return
*/
@ -38,7 +41,7 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/**
*
*
*
* @param sysAgriInfo
* @return
*/
@ -46,7 +49,7 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/**
*
*
*
* @param ids
* @return
*/
@ -54,9 +57,27 @@ public interface ISysAgriInfoService extends IService<SysAgriInfo> {
/**
*
*
*
* @param id
* @return
*/
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;
import java.util.List;
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
*
*
* @author agri
* @date 2025-12-23
*/
public interface ISysDtuDataService
public interface ISysDtuDataService extends IService<SysDtuData>
{
/**
* DTU湿
*
*
* @param id DTU湿
* @return DTU湿
*/
@ -21,7 +26,7 @@ public interface ISysDtuDataService
/**
* DTU湿
*
*
* @param sysDtuData DTU湿
* @return DTU湿
*/
@ -36,7 +41,7 @@ public interface ISysDtuDataService
/**
* DTU湿
*
*
* @param sysDtuData DTU湿
* @return
*/
@ -44,7 +49,7 @@ public interface ISysDtuDataService
/**
* DTU湿
*
*
* @param sysDtuData DTU湿
* @return
*/
@ -52,7 +57,7 @@ public interface ISysDtuDataService
/**
* DTU湿
*
*
* @param ids DTU湿
* @return
*/
@ -60,9 +65,15 @@ public interface ISysDtuDataService
/**
* DTU湿
*
*
* @param id DTU湿
* @return
*/
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