Compare commits

...

367 Commits

Author SHA1 Message Date
2b3be8636f 1.单体电池批量新增修改 2026-04-16 19:49:56 +08:00
5eb9b455a5 1.单体电池批量新增修改 2026-04-15 22:47:17 +08:00
4947f085c7 增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改 2026-04-14 16:44:31 +08:00
5460aadf6c 增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改 2026-04-14 16:11:23 +08:00
79a8040446 1.一周充放曲线改为了时间聚合柱状图。
2.PCS最高温度修复bug展示多PCS设备的数据
3.PCS的状态根据状态枚举映射配置的内容显示
4.BMS的总览,增加工作状态、与PCS通讯、与EMS通讯的配置,及关联展示
5.增加批量导入单体电池点位的功能
6.修复计算点可能会出现id与code在一个池子内的问题,再次计算后数据正常
7.计算点增加小数位限制的功能,实时计算与7天历史接口都已经按照配置的小数位进行限制
8.统计报表中的功率曲线改为了按照分钟显示
9.功率曲线出现断点的问题是因为数据计算太密集了导致的,增加了前端连线不断的显示
10.PCS和电池堆的曲线与配置增加了关联设备显示
11.点位映射中的电池温度,增加了多设备
12.收益报表增加升序排列,合并当月所有合计
13.增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改
2026-04-12 15:16:57 +08:00
ed36292c94 Merge pull request 'dev' (#3) from dev into waibao
Reviewed-on: #3
2026-04-09 01:31:05 +00:00
515f440298 重构 2026-03-23 13:47:20 +08:00
e4cfd15cb4 重构 2026-03-18 10:06:42 +08:00
5ab2cb8f90 重构 2026-02-22 18:59:40 +08:00
4e7d387edf 重构 2026-02-17 21:42:59 +08:00
49ed5f218a 重构 2026-02-16 14:46:27 +08:00
8806473080 重构 2026-02-16 13:41:35 +08:00
71d0b0f609 重构 2026-02-15 16:02:06 +08:00
6253fb6b2d 临时修改 2026-02-13 21:41:23 +08:00
21673ecd1e 临时修改 2026-02-12 21:07:41 +08:00
66de6fe77c Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java
#	ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java
2026-02-12 21:06:36 +08:00
63ed2641ee 临时修改 2026-02-12 21:05:11 +08:00
5d5a7137fc Merge pull request 'dev' (#2) from dev into main
Reviewed-on: #2
2026-02-11 01:55:45 +00:00
zq
6545b4f947 修改 2026-01-28 19:26:15 +08:00
zq
8a44009c42 修改设备监控电表类型设备曲线不展示问题 2026-01-28 19:21:30 +08:00
zq
2b22b70baa 新增电表报表、收益报表导出功能 2026-01-28 15:59:30 +08:00
zq
5a86769b40 统计报表-功率曲线按照分钟展示数据 2026-01-27 19:15:55 +08:00
zq
eeccd19f0a 设备监控页面-设备告警铃铛数由查询告警点位数据改为查询设备故障告警 2026-01-27 17:54:11 +08:00
zq
d19f07c4e8 modbus本地设备数据读取失败,告警等级修改为“一般”,读到数据后自恢复 2026-01-27 17:37:42 +08:00
zq
9f2c303047 topic设备数据消息5次没有数据内容则增加告警,告警等级修改为“一般”,读到数据后自恢复 2026-01-27 17:26:11 +08:00
zq
7199677d7b 解决null值转换为字符串 "null"问题 2026-01-27 17:23:32 +08:00
zq
4b58838d5d 解决null值转换为字符串 "null"问题 2026-01-27 16:55:04 +08:00
zq
14904ea6b2 站点首页-设备告警仅存在严重、紧急告警时提醒 2026-01-27 16:49:20 +08:00
zq
5225f03195 站点首页-当日功率曲线按照分钟展示数据 2026-01-27 16:47:48 +08:00
zq
619c05e965 修改消防数据查询报错问题 2026-01-26 16:46:31 +08:00
zq
1531af2969 修改消防数据查询报错问题 2026-01-26 14:38:16 +08:00
e55cdc6504 临时修改 2026-01-25 18:47:38 +08:00
400ae98b4e 临时修改 2026-01-25 14:00:18 +08:00
zq
dd060b0abf 单体电池解析数据改动;枚举值匹配转换改动 2026-01-24 20:51:16 +08:00
6ae9126662 回滚到 19:29 2026-01-24 00:20:27 +08:00
83fae710fb 修改小错误 2026-01-23 23:46:46 +08:00
c518b9bcae mqtt转移到测试环境,生产由电动所的应用提供 mqtt 数据 2026-01-23 23:09:31 +08:00
034c682864 batteryDataProcessFromBmsc方法device_id改成 device_id+cluster_id 2026-01-23 21:05:07 +08:00
5f7604d36b 加了日志,device_id改成 device_id+cluster_id 2026-01-23 20:16:05 +08:00
zq
084201bb58 修改 2026-01-23 19:43:48 +08:00
zq
194429af2d 修改 2026-01-23 19:29:24 +08:00
zq
b3f7ca7a81 设备监控-实时运行页面曲线按照每分钟展示 2026-01-23 19:11:13 +08:00
zq
5eccb35568 修改 2026-01-23 17:38:26 +08:00
zq
c007016224 告警消息按时间倒序查询 2026-01-23 15:30:07 +08:00
zq
768dd0bead 电表设备详情其他类型展示值查询 2026-01-23 15:29:44 +08:00
zq
26c534cae9 修改 2026-01-23 11:20:05 +08:00
zq
24ba30a536 逆变器功率上限值,默认为100kW 2026-01-23 10:40:26 +08:00
zq
bb85ca1d5e 修改 2026-01-22 18:12:40 +08:00
zq
ff76a73a61 修改 2026-01-22 18:03:47 +08:00
zq
1db167f0f8 Merge branch 'dev' of http://101.43.41.9:13000/xzzn/emsback into dev 2026-01-22 17:53:43 +08:00
f592b709a7 解决报错问题 2026-01-22 16:06:52 +08:00
zq
7f025be41b 站点首页新增展示字段:昨日充电量、昨日放电量、昨日实时收入 2026-01-22 15:40:29 +08:00
8968dc7a41 Merge remote-tracking branch 'origin/dev' into dev 2026-01-22 15:08:50 +08:00
46ea4acc2c 设备列表不展示display_flg=1的数据 2026-01-22 15:08:39 +08:00
zq
04bf9fe1c1 收益报表新增一列”实际收益“,价格保留三位小数 2026-01-22 14:30:52 +08:00
zq
b4f867f796 解决查询空指针报错问题 2026-01-22 14:17:07 +08:00
zq
ecc4799afd 修改查询策略语句 2026-01-22 09:47:47 +08:00
3287cddc6a 用户信息增加 belongsite 2026-01-21 14:53:21 +08:00
11e217787b 修正 modbus 版本为 3.1.0 2026-01-20 21:58:11 +08:00
fec45dac03 修正 modbus 长连接 2026-01-20 21:22:35 +08:00
939bcbe950 修正 modbus 写入超时问题 2026-01-20 19:44:10 +08:00
12a459854e 修正 modbus 超时问题 2026-01-20 17:21:12 +08:00
a31a1a1caa 修正 modbus 超时问题 2026-01-20 17:19:35 +08:00
zq
8716d43879 修改modbus读取设备数据连接池配置 2026-01-20 10:54:14 +08:00
zq
dd0132ab2f 解决保护方案告警不触发问题 2026-01-16 17:06:31 +08:00
zq
fb64be5a5a PCS关机先向设备发送给定功率值再发送关机指令 2026-01-15 17:49:03 +08:00
zq
0076872134 解决保护方案告警不触发问题 2026-01-15 17:40:51 +08:00
zq
f05ba30f14 修改设备监控-PCS实时有功功率与总交流有功功率不一致问题 2026-01-15 15:21:26 +08:00
zq
9aa7dd9d18 运行策略测试修改 2026-01-15 14:45:28 +08:00
zq
3f4d3772b0 设备告警点位解析修改 2026-01-12 15:18:21 +08:00
zq
685a2e8687 工作状态解析格式修改 2026-01-12 13:26:40 +08:00
zq
ebc06e2a2d 运行策略防逆流逻辑修改 2026-01-12 13:25:34 +08:00
zq
2fe5d7b8a1 获取设备列表参数:站点ID不必传 2026-01-08 17:19:01 +08:00
zq
31fe793e97 开关机功能增加设备参数校验 2026-01-08 17:16:24 +08:00
zq
1eee07e040 设备配置表新增work_status字段 2026-01-08 16:46:24 +08:00
zq
566119f692 设备告警点位数据解析逻辑修改 2026-01-08 15:33:54 +08:00
zq
77a3b6e855 设备在线状态和工作状态取值调整 2026-01-07 11:27:02 +08:00
zq
a4ac11e7d9 解决策略同步数据不对和重复问题 2026-01-06 11:27:20 +08:00
zq
4f3cd8c82b 解决策略同步开始和结束时间格式解析报错 2026-01-06 10:26:39 +08:00
zq
873119a1ff 设备上送数据解析判断设备类型 2026-01-06 10:25:08 +08:00
zq
6ed7a25819 修改切面代理配置 2026-01-05 16:25:46 +08:00
zq
dbdf1a5a61 PCS开关机相关配置不在数据表维护 2026-01-05 14:23:30 +08:00
zq
2278b63ada 运行策略轮询PCS设备配置读取逻辑修改 2026-01-04 11:07:35 +08:00
zq
b032e8e073 运行策略轮询PCS设备配置读取逻辑修改 2026-01-04 11:06:03 +08:00
zq
f24072f248 PCS设备开关机逻辑修改 2026-01-04 10:40:38 +08:00
zq
041c41822e PCS设备配置功能修改 2026-01-04 09:00:10 +08:00
zq
552d471fde 策略下发日志修改 2025-12-30 15:25:59 +08:00
zq
e381ff86c8 设备监控-杭州湾电表数据不显示问题修改 2025-12-30 15:06:42 +08:00
zq
2c9cc1f1d4 运行策略轮询放电逻辑修改 2025-12-30 14:36:11 +08:00
zq
93ed334c0e 新增PCS设备配置功能 2025-12-29 18:07:30 +08:00
zq
bac66aaddb 新增PCS设备配置功能 2025-12-29 17:45:53 +08:00
zq
5456d4742e 新增运行策略轮询功能 2025-12-29 15:47:28 +08:00
zq
22415df926 没有未处理的故障告警,站点首页不显示“设备告警”提示 2025-12-26 10:58:46 +08:00
zq
9e086b77b5 运行策略页面开始和结束时间显示按照升序排序 2025-12-26 10:43:12 +08:00
zq
56613e8f70 新增PCS点位字段配置-电池簇数 2025-12-25 11:42:06 +08:00
zq
18ff0100b9 modbus读取本地设备数据增加告警逻辑 2025-12-24 16:19:04 +08:00
zq
90e9b004b4 统计报表-电表报表:计算本次与上次数据差值,累加到对应的数据类型尖、峰、平、谷里面 2025-12-23 16:12:04 +08:00
zq
4f43b043ac 统计报表-收益报表:统一逻辑,电表尖、峰、平、谷差值直接乘以对应类型电价 2025-12-23 15:43:53 +08:00
zq
eb6f44dd93 解决收益报表分页问题 2025-12-23 14:58:43 +08:00
zq
d0bab53dee 统计报表-收益报表:统一逻辑,电表尖、峰、平、谷差值直接乘以对应类型电价 2025-12-23 14:43:17 +08:00
zq
289fbc4d3a 统计报表-功率曲线:统一逻辑,储能功能取值字段secondary_total_active_power修改为取total_active_power;负荷功率=电网功率+光伏功率-储能功率 2025-12-23 14:09:17 +08:00
zq
21fe3af17c 统计报表-概率统计:统一按照电动所逻辑,从储能电表METE中取,今日总数据-昨日总数据=今日充放电 2025-12-23 13:34:44 +08:00
zq
944adf3503 解决云上-生产环境空指针报错 2025-12-23 09:50:27 +08:00
zq
15a25b5ada 新增设备点位数据转换公式字段 2025-12-22 14:02:57 +08:00
zq
289fd37e2b 解决本地端解析数据报错问题 2025-12-19 16:41:49 +08:00
zq
2cd4040431 解决设备列表页面分页展示数据不对问题 2025-12-19 15:04:04 +08:00
zq
245b3e22d6 PCS开关机功能通过modbus连接设备发送控制命令; 2025-12-19 11:50:11 +08:00
zq
ff487f1b05 集成modbus连接本地设备读取数据代码-配置文件方式读取;
PCS开关机功能通过modbus连接设备发送控制命令;
2025-12-19 11:38:04 +08:00
zq
16d414f092 平台修改意见20251120-修改上传点位清单导入报错问题,生产不产生日志 2025-12-16 12:33:55 +08:00
zq
41b846c7f9 解决生产环境接口空指针报错 2025-12-15 15:36:24 +08:00
zq
6af6232b51 解决生产环境接口空指针报错 2025-12-15 15:32:21 +08:00
zq
40e23df66a 平台修改意见20251120-设备列表查询接口增加设备类型参数 2025-12-15 14:58:33 +08:00
zq
3611e444da 平台修改意见20251120-设备列表查询接口增加设备类型参数 2025-12-15 11:07:14 +08:00
zq
7b25674a25 Merge branch 'dev' of http://101.43.41.9:13000/xzzn/emsback into dev 2025-12-15 10:55:45 +08:00
zq
d387612b07 平台修改意见20251120-设备详情查询告警点位数据报错问题 2025-12-15 10:55:40 +08:00
zq
85b370ea4c 平台修改意见20251120-点位上传根据设备ID区分 2025-12-12 14:36:18 +08:00
zq
c6fca8b939 平台修改意见20251120-新增PCS设备开关机 2025-12-12 13:25:53 +08:00
zq
e73787ee8f 平台修改意见20251120-统一PCS页面与设备列表页面的设备状态取值字段 2025-12-12 11:06:22 +08:00
zq
0e1c7f52f1 平台修改意见20251120-调整表ems_devices_setting字段running_status为device_status,与表ems_pcs_data字段device_status保持一致 2025-12-12 10:47:21 +08:00
zq
73c668709a 平台修改意见20251120-点位上传根据设备ID区分 2025-12-11 16:13:39 +08:00
zq
3488808786 平台修改意见20251120-综合查询-按天查询点位数据箱线图展示 2025-12-09 17:23:24 +08:00
zq
497e5bb8ca 去除导入的不存在类 2025-12-09 16:58:52 +08:00
zq
678e814350 平台修改意见20251120-新增EMS设备类型查询列表接口;综合查询-按天查询点位数据箱线图展示;处理EMS设备topic消息;设备类型接口新增告警数量 2025-12-09 16:47:34 +08:00
zq
5f1e621da2 平台修改意见20251120-点位图表的日数据采用箱线图展示 2025-12-09 14:28:52 +08:00
zq
b7aaf85a3f 平台修改意见20251120-点位上传新增数据枚举映射处理逻辑 2025-12-08 18:48:05 +08:00
zq
28dfb0c152 平台修改意见20251120-统计报表-收益报表 2025-12-07 18:35:32 +08:00
zq
fc5f56b3b0 平台修改意见20251120-修改电表每日充放电数据hardcode 2025-12-05 19:15:04 +08:00
zq
3313c7f9cd 平台修改意见20251120-动环设备新增字段-数据更新时间 2025-12-05 18:33:33 +08:00
zq
70c990051b 平台修改意见20251120-液冷设备新增字段-数据更新时间 2025-12-05 18:25:39 +08:00
zq
b4b78e03b1 平台修改意见20251120-BMS电池堆新增字段-数据更新时间 2025-12-05 18:16:50 +08:00
zq
3605702209 平台修改意见20251120-BMS电池簇新增字段-数据更新时间 2025-12-05 18:07:12 +08:00
zq
62340387bb 平台修改意见20251120-优化点位清单导出内容 2025-12-05 17:15:13 +08:00
zq
a31b607872 平台修改意见20251120-告警点位清单查询 2025-12-05 08:58:00 +08:00
zq
8e631b7b30 平台修改意见20251120-告警点位清单查询 2025-12-05 08:57:06 +08:00
zq
3735c4f4d8 平台修改意见20251120-设备点位匹配解析;上传点位清单修改; 2025-12-05 08:46:19 +08:00
zq
c068e7d4ab 修复站点-电动所内部实时运行查询报错问题 2025-11-30 10:26:03 +08:00
zq
b38bc36ad6 平台修改意见20251120-021_FXX_01站点设备点位初始化配置,读取配置的设备点位匹配关系,存储对应的点位数据 2025-11-29 19:54:10 +08:00
d9c0ff733a 平台修改意见20251120-新增设备点位清单上传接口 2025-11-28 11:58:45 +08:00
e87abc2807 平台修改意见20251120-设备监控类别新增动环、消防设备数据 2025-11-26 19:07:38 +08:00
a5e977c0d1 平台修改意见20251120-设备监控实时运行页面数据图按照小时展示,共用时间范围,新增时间查询条件 2025-11-25 14:23:26 +08:00
7298acc785 修改日志目录 2025-11-19 14:05:40 +08:00
55d648d12c 增加本地配置 2025-11-19 13:28:09 +08:00
36f08e5f06 漏提交-自定义注解 2025-11-19 10:05:51 +08:00
1cc7d53559 工具类 2025-11-17 12:15:07 +08:00
3753075b10 同步日志&监听处理 2025-11-17 02:47:43 +08:00
aab2b1d94e 【验收】2 运行策略配置同步 2025-11-17 02:45:35 +08:00
3ee99fa8d4 【验收】6-保护方案告警同步云端&& 【验收】7-下发操作日志同步云端 2025-11-16 22:48:14 +08:00
c62aa46fcb 【验收】3 修改 2025-11-16 22:27:53 +08:00
bfbb0ae475 【验收】3 修改 2025-11-16 21:22:09 +08:00
25e03ab028 【验收】8 本地设备切换记录日志 2025-11-16 21:00:29 +08:00
38ade0c2ed 验收3-设备保护告警同步本地 2025-11-13 00:02:47 +08:00
f1e819ba2b 生产mqtt配置 2025-11-12 23:21:04 +08:00
be4da8c58d 生产mqtt配置 2025-11-12 15:26:30 +08:00
fa280097eb 配置文件修改 2025-11-11 17:14:40 +08:00
1b5fcea5cd 配置文件修改 2025-11-11 15:42:19 +08:00
49a83fd420 验收2-同步策略运行 2025-11-11 10:37:46 +08:00
5381cd597c mqtt配置修改 2025-11-11 10:37:04 +08:00
43edc47aaa 验收5-mqtt配置 2025-11-06 19:39:53 +08:00
72c7c0c3e0 点位清单-导入导出 2025-11-06 15:32:33 +08:00
c2682f38a8 点位清单-导入导出 2025-11-05 14:58:05 +08:00
3598cb2d66 告警保护方案轮询-调整 2025-10-30 16:52:06 +08:00
82e63e28d3 告警保护方案轮询-初稿 2025-10-29 18:38:34 +08:00
614cca4297 定时任务迁移到quartz模块下 2025-10-28 14:38:18 +08:00
d83af112e7 获取指定站点下的指定设备类型的设备 2025-10-28 14:22:22 +08:00
75bd5f6bf2 单站监控-设备状态同设备列表 2025-10-28 10:20:12 +08:00
0108b4f108 task61-告警保护方案增删改查 2025-10-24 19:08:57 +08:00
4a0075b606 告警数据按设备存表 2025-10-22 17:21:43 +08:00
b776f6ff76 列表界面里面的设备类型列改成显示设备类别 2025-10-21 15:25:23 +08:00
76b30c715c 0918优化-综合查询最大最小加日期 2025-10-21 15:12:14 +08:00
e1fb6e30ac 消防数据存表 2025-10-21 13:55:06 +08:00
a338c921d3 综合查询优化 2025-10-21 12:39:14 +08:00
4ad2cafc5d 0918优化-综合查询显示曲线最大最小平均值和差值 2025-10-16 17:01:35 +08:00
d0275e62e0 电价-实时收入实时计算 2025-10-16 14:57:18 +08:00
94e18c029c 电价-实时收入实时计算 2025-10-16 14:04:13 +08:00
391ed354f8 dds-电池簇同步数据获取父类stackId 2025-10-15 17:53:12 +08:00
6716852435 设备列表-单体电池的点位清单数据 2025-10-15 16:27:22 +08:00
451b2f6766 新增设备获取父类类别的id列表 2025-10-15 16:26:21 +08:00
250318ddef 初始化设备信息存入redis 2025-10-15 15:39:18 +08:00
d468ef9941 task65-动态获取站点下设备类型 2025-10-14 17:32:16 +08:00
15b964b4ce task66-单体电池调综合查询点位曲线接口-增加簇id 2025-10-14 17:07:00 +08:00
3020f9f915 task63-点位清单增加单位列 2025-10-14 16:50:53 +08:00
609803605d 工单优化 2025-10-14 14:36:34 +08:00
aadf66d5ad 点位清单-按数值排序 2025-10-13 14:24:13 +08:00
049eaa84b7 0918电价-站点的实时总收入和实时当日收入 2025-10-13 13:00:35 +08:00
c438f50ae1 0918电价-站点的实时总收入和实时当日收入 2025-10-13 12:04:46 +08:00
eed6f839f6 0918电价-站点的实时总收入和实时当日收入 2025-10-13 11:37:03 +08:00
6ebeb48b62 0918电价-站点的实时总收入和实时当日收入 2025-10-12 19:47:55 +08:00
3454359f01 0918电表报表-奉贤每日尖峰平谷累加 2025-10-11 17:21:40 +08:00
83f8f4e293 电价配置-增加站点id 2025-10-11 16:31:32 +08:00
b79f9caa2d 电价配置-增加站点id 2025-10-11 16:28:50 +08:00
e33b26fc05 日期转换错误 2025-10-11 10:48:45 +08:00
9075878e41 电价列表-按月降序排序 2025-10-10 18:59:47 +08:00
158bc1a51d 0918优化-电表报表-尖峰平谷日差值显示 2025-10-10 17:32:15 +08:00
b861ad7593 0918优化-电表报表-尖峰平谷日差值显示 2025-10-10 16:10:37 +08:00
636a2ab73b 电价配置-列表加时间范围-暂时只取年份 2025-10-10 11:21:33 +08:00
d8d0a83c87 电价配置 2025-10-09 19:58:50 +08:00
7121fdecfa 0929-部分数据优化 2025-09-29 20:05:16 +08:00
2bbc0abc08 0918优化-告警工单逻辑优化 2025-09-29 10:14:46 +08:00
e5a2ce4c8d 0918优化-电表点位查询修改 2025-09-28 14:44:02 +08:00
f2b2741b1c 0918优化-电表取值修改 2025-09-28 10:32:02 +08:00
097709d403 0918优化-实时运行PCS最高温度数据缺失 2025-09-27 20:01:31 +08:00
33534cc500 0918优化-实时运行曲线图时间格式处理 2025-09-27 15:11:57 +08:00
ee53ab137b 0918优化-点位列表电池簇特殊处理 2025-09-26 16:43:40 +08:00
b2d5023122 0918优化-点位列表电池簇特殊处理 2025-09-26 16:37:46 +08:00
553e30c6ef 0926-奉贤液冷数据接入 2025-09-26 13:55:16 +08:00
102ecbd41c 0918优化-单站监控-实时运行曲线图pcs分开显示 2025-09-25 19:44:00 +08:00
d47d9150e6 0918优化-单站监控-实时运行曲线图取值 2025-09-25 14:53:17 +08:00
46135aa2ec 0918优化-单站监控-功率曲线默认显示前一小时数据按分钟分组 2025-09-24 22:47:09 +08:00
9874d6b1ea 0918优化-单站监控-电表数据修改 2025-09-24 14:12:37 +08:00
f7515b221b 0918优化-点位列表数据值反向搜索 2025-09-24 14:10:10 +08:00
f3b34e7fb5 0918优化-点位列表数据值反向搜索 2025-09-24 14:09:29 +08:00
2810f5e204 0918优化-点位列表数据值反向搜索 2025-09-24 14:07:54 +08:00
7cd5a9b131 0918优化-点位列表排序方式,默认升序 2025-09-23 15:04:02 +08:00
2099b94e73 0918优化-单站监控首页日期修改&点位模糊查询 2025-09-23 14:01:24 +08:00
630f77a783 奉贤 alarm topic接入 2025-09-23 14:00:22 +08:00
8e8c57cb64 奉贤 alarm topic接入 2025-09-23 13:59:45 +08:00
a5f1444984 数据20250916优化-topic订阅失败&数据空报警 2025-09-22 00:40:38 +08:00
e93f9cc6b8 数据20250916优化-点位名称模糊查询 2025-09-18 18:04:59 +08:00
8954329ccc 数据20250916优化-点位最新值+时间 2025-09-18 05:17:57 +08:00
b8d3643ba9 数据20250904优化-功率数据不计算 2025-09-16 10:53:41 +08:00
2451e00022 数据20250904优化-单站监控首页点位数据展示 2025-09-16 00:34:14 +08:00
cb39131871 数据20250904优化-单站监控首页点位数据展示 2025-09-16 00:31:58 +08:00
2bb78dc020 数据20250904优化-功率曲线取值修改 2025-09-15 14:21:27 +08:00
d807b9117d 数据20250904优化-PCS曲线和电池堆曲线返回修改 2025-09-14 22:44:10 +08:00
8a4cff0a6c 数据20250904优化-点位列表分页&点位查询 2025-09-13 21:49:33 +08:00
ecbe55cdbf 数据20250904优化-点位数据接口增加入参 2025-09-13 19:25:58 +08:00
bfdbc4f42c 数据20250904优化 2025-09-12 04:40:47 +08:00
ab6771d33d 20250905-奉贤充放电取值修改 2025-09-07 21:23:24 +08:00
2344cc458c 综合查询页面 2025-09-05 20:31:30 +08:00
aa37e2a881 综合查询页面 2025-09-05 13:45:39 +08:00
15e230babb 优化 2025-09-01 16:17:36 +08:00
7374d7708b 20250808优化-充放电逻辑修改 2025-09-01 16:10:17 +08:00
26bbe6deee 20250808优化-充放电逻辑修改 2025-08-28 14:15:19 +08:00
1a1558f8cd 20250808优化-告警优化&主路线图优化 2025-08-27 01:32:05 +08:00
d6276dc21f 20250808优化-单体电池表拆分优化 2025-08-24 17:12:42 +08:00
725ae14548 20250808优化-单体电池表拆分为分钟,时,天,月级四个表 2025-08-23 00:09:02 +08:00
b2527507e4 20250808优化-单体电池优化 2025-08-16 10:28:06 +08:00
ff058dceaf 20250808优化-单体电池优化 2025-08-13 23:41:34 +08:00
76d0634ae8 20250808优化-单站首页-策略显示 2025-08-13 18:31:14 +08:00
eb72542b67 20250808优化-故障告警 2025-08-11 19:56:51 +08:00
7927066058 20250808优化-设备监控 2025-08-11 14:49:18 +08:00
08c83c1a37 漏提交 2025-08-08 00:13:17 +08:00
f52f253344 误提交-回退 2025-08-04 16:33:45 +08:00
90315d8e2c DDS页面数据设置 2025-08-04 16:23:04 +08:00
97c667bd77 获取堆下面的簇列表入参+站点id 2025-07-29 18:38:21 +08:00
a12f7820cf DDS监控逻辑修改 2025-07-29 18:28:17 +08:00
8fd2f45e18 DDS数据同步 2025-07-29 15:58:26 +08:00
9da5047148 DDS数据同步 2025-07-27 17:34:38 +08:00
281e0f57c4 modbus轮询修改 2025-07-27 13:49:36 +08:00
7ea52a8972 策略曲线图修改 2025-07-22 22:49:54 +08:00
e3e3dc1d96 策略曲线图修改 2025-07-22 22:27:12 +08:00
b4cbf0b34e 电量指标-修改 2025-07-22 13:54:07 +08:00
cbcaaaffb6 DDS数据同步修改-load 2025-07-21 11:01:22 +08:00
c51c4be498 优化sql 2025-07-18 20:14:38 +08:00
51e6936d77 设备列表-返回新增parentId 2025-07-18 10:35:32 +08:00
b25f5310e1 DDS监听数据结构修改 2025-07-18 01:45:09 +08:00
34c2647495 单体电池每日最新数据处理 2025-07-18 01:38:30 +08:00
41af74b33a jar包替換 2025-07-17 17:12:44 +08:00
9125ce735c 策略数据下发大概逻辑 2025-07-17 15:58:08 +08:00
a4338b3995 单个单体电池数据曲线图 2025-07-17 13:31:25 +08:00
1da9867372 modbus协议轮询设备信息 2025-07-17 10:13:19 +08:00
e2663b18d5 工单管理-工单列表入参 2025-07-16 16:18:35 +08:00
587e9f86b1 工单管理-工单详情新增返回字段 2025-07-15 19:31:07 +08:00
161c54b6ef 工单管理-新增字段 2025-07-15 17:59:57 +08:00
af32d62c80 Mqtt数据插入 2025-07-15 15:34:13 +08:00
1e970c9d19 单体电池-按deviceId排序 2025-07-15 13:37:58 +08:00
e6d6f806e4 策略配置-曲线图模型修改 2025-07-15 12:57:00 +08:00
d22a71739f Merge remote-tracking branch 'origin/dev' into dev 2025-07-14 16:16:19 +08:00
bf1d70c953 修改 mqtt 源数据存储 2025-07-14 16:16:14 +08:00
cceafdca92 log级别修改 2025-07-14 10:53:36 +08:00
3e6cd1b6ed 策略配置-模板配置修改 2025-07-13 16:27:31 +08:00
41656581ca 策略配置-模板配置修改 2025-07-13 15:42:47 +08:00
9fcc81fbac 策略配置-时间配置字段修改 2025-07-13 15:27:38 +08:00
a0b83fc8a4 策略配置-时间配置字段修改 2025-07-13 15:02:00 +08:00
819e4cd3a7 策略配置-字段修改 2025-07-13 14:14:07 +08:00
7b798f8323 策略配置-策略模板修改 2025-07-13 11:58:40 +08:00
acf2164c1c 策略配置-策略时间配置 2025-07-13 00:42:43 +08:00
23f7e70df6 策略配置-策略运行修改 2025-07-12 23:17:41 +08:00
7bd6341c9b 策略配置-模板修改 2025-07-12 22:36:34 +08:00
aa751464b4 策略配置-模板修改&模板时间配置 2025-07-12 18:51:23 +08:00
eafb0ec2c6 设备列表-上传设备图像 2025-07-12 14:54:18 +08:00
118be05c8a 设备列表-设备增删改查 2025-07-12 14:48:35 +08:00
7dc664446e 设备列表-不分页 2025-07-11 20:58:55 +08:00
3b55f3d053 策略配置-策略详情信息 2025-07-11 19:47:15 +08:00
84757ab358 工单管理-字段类型修改 2025-07-11 16:43:47 +08:00
05a2af8c92 工单管理-改动 2025-07-11 15:04:46 +08:00
020d145a1f 策略配置-策略运行 2025-07-10 22:30:56 +08:00
d6ec98eab9 工单管理优化 2025-07-10 17:47:19 +08:00
21ce358638 增加 dds 数据解析 2025-07-10 17:03:07 +08:00
8ce403eff2 问题优化 2025-07-10 12:57:06 +08:00
d37fb949e1 问题优化 2025-07-10 12:42:04 +08:00
406cc59b25 统计报表-功率曲线 2025-07-09 22:41:14 +08:00
53f515ad6c 统计报表-电表报表 2025-07-09 19:15:59 +08:00
29c5ffc15c 首页-数据概览 2025-07-09 16:57:41 +08:00
3eb8636e53 统计报表-sql优化 2025-07-09 14:36:11 +08:00
84d77c2d33 统计报表-获取pcs列表 2025-07-08 20:13:24 +08:00
b0264ebe46 统计报表-电池温度数据 2025-07-08 16:43:33 +08:00
13266bdbd5 统计报表-电池堆曲线 2025-07-08 14:46:42 +08:00
7d1f1836d8 统计报表-pcs曲线 2025-07-07 22:15:01 +08:00
11a95c19fd pcs表新增字段 2025-07-07 15:32:30 +08:00
b2a964cb82 实时运行逻辑 2025-07-07 15:00:59 +08:00
733afa99bd 电表类别-枚举 2025-07-05 23:24:46 +08:00
6a5504434d 电量指标-时间筛选数据 2025-07-05 23:24:13 +08:00
f42e8549b1 单站监控-电表取数逻辑修改 2025-07-04 13:51:42 +08:00
4bb23a7c75 奉贤电表数据接入 2025-07-04 12:37:18 +08:00
126b637932 BMS总览-获取堆下面簇数据 2025-07-03 16:12:19 +08:00
7bf4baf85f 设备列表接口 2025-07-03 15:40:27 +08:00
9dae47934f Merge remote-tracking branch 'origin/dev' into dev 2025-07-03 14:47:33 +08:00
0e726a214d 删除无用代码 2025-07-03 14:47:27 +08:00
7964806b94 站点配置-获取站点列表 2025-07-02 19:43:17 +08:00
b4302edd8a redis key常量 2025-07-02 17:34:09 +08:00
88fee64f46 redis key常量 2025-07-02 17:32:17 +08:00
8081e38d5e 本地调试-回退 2025-07-02 16:10:22 +08:00
8415e2749e 从redis取数 2025-07-02 16:08:50 +08:00
170bae19f8 修正部分参数 2025-07-02 12:26:09 +08:00
37fd07b0c8 修正部分参数 2025-07-02 12:10:46 +08:00
f73ba23b52 修正部分参数 2025-07-02 11:40:23 +08:00
a3f77113c9 字段值修改 2025-07-01 22:09:16 +08:00
a7e33afb19 修正部分参数 2025-07-01 22:03:43 +08:00
2c5d4ddd1b 修正部分参数 2025-07-01 21:58:26 +08:00
9efb87ed24 修正部分参数 2025-07-01 21:50:54 +08:00
4d5f2adef4 修正部分参数 2025-07-01 21:39:32 +08:00
0318b2455d Merge remote-tracking branch 'origin/dev' into dev 2025-07-01 21:28:30 +08:00
b2c624f452 修正部分参数 2025-07-01 21:28:25 +08:00
b207208623 Merge remote-tracking branch 'origin/dev' into dev 2025-07-01 21:22:18 +08:00
259784f525 概率统计-电量指标默认7天 2025-07-01 21:21:55 +08:00
5eb8de692c 修正部分参数 2025-07-01 21:21:20 +08:00
ed025d2750 概率统计-电量指标 2025-07-01 16:25:23 +08:00
d24da5363a 单体电池-增加簇号 2025-07-01 13:28:50 +08:00
342439e3a9 单体电池修改 2025-06-30 23:01:41 +08:00
8663ab8325 单体电池添加入参site_id 2025-06-30 22:22:47 +08:00
93bd88b94c 修正部分参数 2025-06-30 22:08:15 +08:00
7e265e21b1 修正部分参数 2025-06-30 21:54:51 +08:00
d0db9e137a 修正部分参数 2025-06-30 21:47:39 +08:00
790e4ca716 修正部分参数 2025-06-30 21:38:17 +08:00
62e4608a7a Merge remote-tracking branch 'origin/dev' into dev 2025-06-30 21:15:23 +08:00
9fbc6dc6b8 修改 mqtt 密码 2025-06-30 21:15:20 +08:00
3d71b9caba 枚举 2025-06-30 20:33:57 +08:00
a6af1397e9 枚举 2025-06-30 20:01:55 +08:00
2a5ac78394 Merge remote-tracking branch 'origin/dev' into dev 2025-06-30 18:59:00 +08:00
f2e5e07857 紧急修改 siteid 2025-06-30 18:58:53 +08:00
fbab6ea631 电表逻辑修改 2025-06-30 18:14:24 +08:00
337599d1c6 增加工单配置文件 2025-06-30 13:39:51 +08:00
cebd845af1 sql优化 2025-06-30 13:11:05 +08:00
49a3ce2c11 增加mqtt账号密码 2025-06-30 09:46:31 +08:00
551ae90155 删除 lombok 2025-06-29 21:21:37 +08:00
d0b98a0a8a 奉贤数据单独控制 2025-06-29 16:20:13 +08:00
aaacafc0c3 Merge remote-tracking branch 'origin/dev' into dev 2025-06-29 15:41:29 +08:00
c6fc7c00cc 奉贤数据单独控制 2025-06-29 15:41:24 +08:00
ae59143771 单站监控-故障告警 2025-06-29 15:38:31 +08:00
1225559c36 pcs 支路 2025-06-29 15:19:01 +08:00
324beeaa69 优化 2025-06-29 14:45:20 +08:00
aed3f830f5 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	ems-system/src/main/java/com/xzzn/ems/mapper/EmsBatteryDataMapper.java
#	ems-system/src/main/resources/mapper/ems/EmsBatteryDataMapper.xml
2025-06-29 14:04:55 +08:00
747ce8a08d 数据分次提交改成一次性提交 2025-06-29 14:03:24 +08:00
d2824b6087 单站监控-单体电池列表分页 2025-06-29 13:40:37 +08:00
bb0f972f55 奉贤数据单独使用 job处理 2025-06-29 13:13:23 +08:00
d1aa8dbd2c 修改site_id & device_id 2025-06-29 10:55:05 +08:00
177e235c0e 修改 device id 2025-06-28 23:07:49 +08:00
1a56bd6526 修改 device id 2025-06-28 21:16:48 +08:00
38e93c9681 修改 device id 2025-06-28 21:11:23 +08:00
73f62c8fa5 单站监控-电表 2025-06-28 14:56:22 +08:00
365dd819b5 奉贤数据接入 2025-06-27 17:08:26 +08:00
31dc8e72c6 mqtt配置 2025-06-27 14:07:45 +08:00
da894c26d1 Merge remote-tracking branch 'origin/dev' into dev 2025-06-27 12:50:58 +08:00
f439228432 mqtt配置 2025-06-27 12:49:14 +08:00
3609b03deb 单体电池-框架 2025-06-27 10:14:15 +08:00
0544929d07 Merge pull request 'work init' (#1) from main into dev
Reviewed-on: #1
2025-06-26 08:22:37 +00:00
449 changed files with 69119 additions and 1216 deletions

View File

@ -39,8 +39,9 @@
<!-- Mysql驱动包 --> <!-- Mysql驱动包 -->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency> </dependency>
<!-- 核心模块--> <!-- 核心模块-->

View File

@ -0,0 +1,75 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsAlarmRecords;
import com.xzzn.ems.domain.vo.AlarmRecordListRequestVo;
import com.xzzn.ems.domain.vo.AlarmRecordListResponseVo;
import com.xzzn.ems.service.IEmsAlarmRecordsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 单站监控-故障告警
*
* @author xzzn
*/
@RestController
@RequestMapping("/ems/siteAlarm")
public class EmsAlarmRecordsController extends BaseController
{
@Autowired
private IEmsAlarmRecordsService iEmsAlarmRecordsService;
/**
* 获取告警详情列表
*/
@GetMapping("/getAlarmDetailList")
public TableDataInfo getAlarmDetailList(AlarmRecordListRequestVo requestVo)
{
startPage();
List<AlarmRecordListResponseVo> list = iEmsAlarmRecordsService.getAlarmRecordDetailList(requestVo);
return getDataTable(list);
}
/**
* 生成工单
*/
@PostMapping("/createTicketNo")
public AjaxResult createTicketNo(@RequestBody EmsAlarmRecords emsAlarmRecords)
{
Long id = emsAlarmRecords.getId();
if (id == null) {
return error("告警id不能为空");
}
String ticketNo= iEmsAlarmRecordsService.createTicketNo(id,getUserId());
if (StringUtils.isNotEmpty(ticketNo) && ticketNo.contains("T")) {
return AjaxResult.success("操作成功", ticketNo);
} else {
return error(ticketNo);
}
}
/**
* 关闭告警
*/
@PostMapping("/closeAlarm")
public AjaxResult closeAlarm(@RequestBody EmsAlarmRecords emsAlarmRecords)
{
Long id = emsAlarmRecords.getId();
if (id == null) {
return error("告警id不能为空");
}
String result = iEmsAlarmRecordsService.closeAlarm(id, getUserId());
if ("success".equals(result) || "告警已关闭".equals(result)) {
return AjaxResult.success("操作成功");
}
return error(result);
}
}

View File

@ -0,0 +1,61 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsDailyChargeData;
import com.xzzn.ems.service.IEmsDailyChargeDataService;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;
/**
* 站点管理-充放电修正 Controller
*/
@RestController
@RequestMapping("/ems/dailyChargeData")
public class EmsDailyChargeDataController extends BaseController {
@Autowired
private IEmsDailyChargeDataService emsDailyChargeDataService;
@GetMapping("/list")
public TableDataInfo list(EmsDailyChargeData emsDailyChargeData) {
startPage();
List<EmsDailyChargeData> list = emsDailyChargeDataService.selectEmsDailyChargeDataList(emsDailyChargeData);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(emsDailyChargeDataService.selectEmsDailyChargeDataById(id));
}
@Log(title = "站点管理-充放电修正", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsDailyChargeData emsDailyChargeData) {
return toAjax(emsDailyChargeDataService.insertEmsDailyChargeData(emsDailyChargeData, getUsername()));
}
@Log(title = "站点管理-充放电修正", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsDailyChargeData emsDailyChargeData) {
return toAjax(emsDailyChargeDataService.updateEmsDailyChargeData(emsDailyChargeData, getUsername()));
}
@Log(title = "站点管理-充放电修正", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(emsDailyChargeDataService.deleteEmsDailyChargeDataByIds(ids));
}
}

View File

@ -0,0 +1,61 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsDailyEnergyData;
import com.xzzn.ems.service.IEmsDailyEnergyDataService;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;
/**
* 站点管理-数据修正 Controller
*/
@RestController
@RequestMapping("/ems/dailyEnergyData")
public class EmsDailyEnergyDataController extends BaseController {
@Autowired
private IEmsDailyEnergyDataService emsDailyEnergyDataService;
@GetMapping("/list")
public TableDataInfo list(EmsDailyEnergyData emsDailyEnergyData) {
startPage();
List<EmsDailyEnergyData> list = emsDailyEnergyDataService.selectEmsDailyEnergyDataList(emsDailyEnergyData);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(emsDailyEnergyDataService.selectEmsDailyEnergyDataById(id));
}
@Log(title = "站点管理-数据修正", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsDailyEnergyData emsDailyEnergyData) {
return toAjax(emsDailyEnergyDataService.insertEmsDailyEnergyData(emsDailyEnergyData, getUsername()));
}
@Log(title = "站点管理-数据修正", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsDailyEnergyData emsDailyEnergyData) {
return toAjax(emsDailyEnergyDataService.updateEmsDailyEnergyData(emsDailyEnergyData, getUsername()));
}
@Log(title = "站点管理-数据修正", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(emsDailyEnergyDataService.deleteEmsDailyEnergyDataByIds(ids));
}
}

View File

@ -0,0 +1,82 @@
package com.xzzn.web.controller.ems;
import java.util.List;
import com.xzzn.ems.domain.vo.EnergyPriceVo;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.service.IEmsEnergyPriceConfigService;
import com.xzzn.common.core.page.TableDataInfo;
/**
* 电价配置Controller
*
* @author xzzn
* @date 2025-09-28
*/
@RestController
@RequestMapping("/ems/energyPriceConfig")
public class EmsEnergyPriceConfigController extends BaseController
{
@Autowired
private IEmsEnergyPriceConfigService emsEnergyPriceConfigService;
/**
* 查询电价配置列表
*/
@PreAuthorize("@ss.hasPermi('system:config:list')")
@GetMapping("/list")
public TableDataInfo list(String siteId, String startTime,String endTime)
{
List<EnergyPriceVo> list = emsEnergyPriceConfigService.selectEmsEnergyPriceConfigList(siteId,startTime,endTime);
return getDataTable2(list);
}
/**
* 获取电价配置详细信息
*/
@PreAuthorize("@ss.hasPermi('system:config:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(emsEnergyPriceConfigService.selectEmsEnergyPriceConfigById(id));
}
/**
* 新增电价配置
*/
@PreAuthorize("@ss.hasPermi('system:config:add')")
@Log(title = "电价配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EnergyPriceVo priceVo)
{
return toAjax(emsEnergyPriceConfigService.insertEmsEnergyPriceConfig(priceVo));
}
/**
* 修改电价配置
*/
@PreAuthorize("@ss.hasPermi('system:config:edit')")
@Log(title = "电价配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EnergyPriceVo priceVo)
{
return toAjax(emsEnergyPriceConfigService.updateEmsEnergyPriceConfig(priceVo));
}
/**
* 删除电价配置
*/
@PreAuthorize("@ss.hasPermi('system:config:remove')")
@Log(title = "电价配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(emsEnergyPriceConfigService.deleteEmsEnergyPriceConfigByIds(ids));
}
}

View File

@ -0,0 +1,107 @@
package com.xzzn.web.controller.ems;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.xzzn.ems.service.IEmsFaultProtectionPlanService;
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.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsFaultProtectionPlan;
import com.xzzn.common.utils.poi.ExcelUtil;
import com.xzzn.common.core.page.TableDataInfo;
/**
* 故障告警保护方案Controller
*
* @author xzzn
* @date 2025-10-22
*/
@RestController
@RequestMapping("/ems/protectPlan")
public class EmsFaultProtectionPlanController extends BaseController
{
@Autowired
private IEmsFaultProtectionPlanService emsFaultProtectionPlanService;
/**
* 查询故障告警保护方案列表
*/
@PreAuthorize("@ss.hasPermi('system:plan:list')")
@GetMapping("/list")
public TableDataInfo list(EmsFaultProtectionPlan emsFaultProtectionPlan)
{
startPage();
List<EmsFaultProtectionPlan> list = emsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(emsFaultProtectionPlan);
return getDataTable(list);
}
/**
* 导出故障告警保护方案列表
*/
@PreAuthorize("@ss.hasPermi('system:plan:export')")
@Log(title = "故障告警保护方案", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, EmsFaultProtectionPlan emsFaultProtectionPlan)
{
List<EmsFaultProtectionPlan> list = emsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(emsFaultProtectionPlan);
ExcelUtil<EmsFaultProtectionPlan> util = new ExcelUtil<EmsFaultProtectionPlan>(EmsFaultProtectionPlan.class);
util.exportExcel(response, list, "故障告警保护方案数据");
}
/**
* 获取故障告警保护方案详细信息
*/
@PreAuthorize("@ss.hasPermi('system:plan:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(emsFaultProtectionPlanService.selectEmsFaultProtectionPlanById(id));
}
/**
* 新增故障告警保护方案
*/
@PreAuthorize("@ss.hasPermi('system:plan:add')")
@Log(title = "故障告警保护方案", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsFaultProtectionPlan emsFaultProtectionPlan)
{
emsFaultProtectionPlan.setCreateBy(getUsername());
return toAjax(emsFaultProtectionPlanService.insertEmsFaultProtectionPlan(emsFaultProtectionPlan));
}
/**
* 修改故障告警保护方案
*/
@PreAuthorize("@ss.hasPermi('system:plan:edit')")
@Log(title = "故障告警保护方案", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsFaultProtectionPlan emsFaultProtectionPlan)
{
emsFaultProtectionPlan.setUpdateBy(getUsername());
return toAjax(emsFaultProtectionPlanService.updateEmsFaultProtectionPlan(emsFaultProtectionPlan));
}
/**
* 删除故障告警保护方案
*/
@PreAuthorize("@ss.hasPermi('system:plan:remove')")
@Log(title = "故障告警保护方案", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(emsFaultProtectionPlanService.deleteEmsFaultProtectionPlanByIds(ids));
}
}

View File

@ -0,0 +1,78 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.enums.DeviceCategory;
import com.xzzn.ems.domain.vo.*;
import com.xzzn.ems.service.IGeneralQueryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* 综合查询
*
*/
@RestController
@RequestMapping("/ems/generalQuery")
public class EmsGeneralQueryController extends BaseController{
@Autowired
private IGeneralQueryService iGeneralQueryService;
/**
* 获取设备枚举
*/
@GetMapping("/getAllDeviceCategory")
public AjaxResult getDeviceCategory()
{
// 获取所有枚举的信息
List<Map<String, String>> deviceCategoryList = new ArrayList<>();
for (DeviceCategory category : DeviceCategory.values()) {
Map<String, String> categoryMap = new HashMap<>();
categoryMap.put("name", category.getInfo());
categoryMap.put("code", category.getCode());
deviceCategoryList.add(categoryMap);
}
return success(deviceCategoryList);
}
/**
* 点位模糊查询
*/
@PostMapping("/pointFuzzyQuery")
public AjaxResult pointFuzzyQuery(@RequestBody PointNameRequest request)
{
return success(iGeneralQueryService.getPointNameList(request));
}
/**
* 根据点位查询点位数据变化
*/
@PostMapping("/getPointValueList")
public AjaxResult getPointValueList(@RequestBody PointNameRequest request)
{
List<GeneralQueryResponse> result = new ArrayList<>();
try {
result = iGeneralQueryService.getPointValueList(request);
} catch (Exception e) {
logger.error("<UNK>",e);
return error("报错请重试!");
}
return success(result);
}
/**
* 获取设备枚举
*/
@GetMapping("/getAllBatteryIdsBySites/{siteIds}")
public AjaxResult getAllBatteryIdsBySites(@PathVariable String[] siteIds)
{
return success(iGeneralQueryService.getAllBatteryIdsBySites(siteIds));
}
}

View File

@ -34,9 +34,13 @@ public class EmsHomePageController extends BaseController{
* 首页看板数据 * 首页看板数据
*/ */
@GetMapping("/dataList") @GetMapping("/dataList")
public AjaxResult list() public AjaxResult list() throws Exception
{ {
return success(homePageService.getHomePageDataList()); try {
return success(homePageService.getHomePageDataList());
} catch (Exception e) {
return error(e.getMessage());
}
} }
/** /**

View File

@ -0,0 +1,106 @@
package com.xzzn.web.controller.ems;
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.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsMqttTopicConfig;
import com.xzzn.ems.service.IEmsMqttTopicConfigService;
import com.xzzn.common.utils.poi.ExcelUtil;
import com.xzzn.common.core.page.TableDataInfo;
/**
* 站点topic配置Controller
*
* @author xzzn
* @date 2025-11-06
*/
@RestController
@RequestMapping("/ems/mqttConfig")
public class EmsMqttTopicConfigController extends BaseController
{
@Autowired
private IEmsMqttTopicConfigService emsMqttTopicConfigService;
/**
* 查询站点topic配置列表
*/
@PreAuthorize("@ss.hasPermi('system:config:list')")
@GetMapping("/list")
public TableDataInfo list(EmsMqttTopicConfig emsMqttTopicConfig)
{
startPage();
List<EmsMqttTopicConfig> list = emsMqttTopicConfigService.selectEmsMqttTopicConfigList(emsMqttTopicConfig);
return getDataTable(list);
}
/**
* 导出站点topic配置列表
*/
@PreAuthorize("@ss.hasPermi('system:config:export')")
@Log(title = "站点topic配置", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, EmsMqttTopicConfig emsMqttTopicConfig)
{
List<EmsMqttTopicConfig> list = emsMqttTopicConfigService.selectEmsMqttTopicConfigList(emsMqttTopicConfig);
ExcelUtil<EmsMqttTopicConfig> util = new ExcelUtil<EmsMqttTopicConfig>(EmsMqttTopicConfig.class);
util.exportExcel(response, list, "站点topic配置数据");
}
/**
* 获取站点topic配置详细信息
*/
@PreAuthorize("@ss.hasPermi('system:config:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(emsMqttTopicConfigService.selectEmsMqttTopicConfigById(id));
}
/**
* 新增站点topic配置
*/
@PreAuthorize("@ss.hasPermi('system:config:add')")
@Log(title = "站点topic配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsMqttTopicConfig emsMqttTopicConfig)
{
emsMqttTopicConfig.setCreateBy(getUsername());
return toAjax(emsMqttTopicConfigService.insertEmsMqttTopicConfig(emsMqttTopicConfig));
}
/**
* 修改站点topic配置
*/
@PreAuthorize("@ss.hasPermi('system:config:edit')")
@Log(title = "站点topic配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsMqttTopicConfig emsMqttTopicConfig)
{
emsMqttTopicConfig.setUpdateBy(getUsername());
return toAjax(emsMqttTopicConfigService.updateEmsMqttTopicConfig(emsMqttTopicConfig));
}
/**
* 删除站点topic配置
*/
@PreAuthorize("@ss.hasPermi('system:config:remove')")
@Log(title = "站点topic配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(emsMqttTopicConfigService.deleteEmsMqttTopicConfigByIds(ids));
}
}

View File

@ -0,0 +1,51 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsPointCalcConfig;
import com.xzzn.ems.service.IEmsPointCalcConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/ems/pointCalcConfig")
public class EmsPointCalcConfigController extends BaseController {
@Autowired
private IEmsPointCalcConfigService pointCalcConfigService;
@GetMapping("/list")
public TableDataInfo list(EmsPointCalcConfig pointCalcConfig) {
startPage();
List<EmsPointCalcConfig> list = pointCalcConfigService.selectPointCalcConfigList(pointCalcConfig);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(pointCalcConfigService.selectPointCalcConfigById(id));
}
@Log(title = "计算点配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsPointCalcConfig pointCalcConfig) {
return toAjax(pointCalcConfigService.insertPointCalcConfig(pointCalcConfig, getUsername()));
}
@Log(title = "计算点配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsPointCalcConfig pointCalcConfig) {
return toAjax(pointCalcConfigService.updatePointCalcConfig(pointCalcConfig, getUsername()));
}
@Log(title = "计算点配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(pointCalcConfigService.deletePointCalcConfigByIds(ids));
}
}

View File

@ -0,0 +1,103 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsPointConfig;
import com.xzzn.ems.domain.vo.ImportPointTemplateRequest;
import com.xzzn.ems.domain.vo.PointConfigCurveRequest;
import com.xzzn.ems.domain.vo.PointConfigGenerateRecentRequest;
import com.xzzn.ems.domain.vo.PointConfigLatestValueRequest;
import com.xzzn.ems.service.IEmsPointConfigService;
import org.springframework.beans.factory.annotation.Autowired;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/ems/pointConfig")
public class EmsPointConfigController extends BaseController {
@Autowired
private IEmsPointConfigService pointConfigService;
@GetMapping("/list")
public TableDataInfo list(EmsPointConfig pointConfig) {
startPage();
List<EmsPointConfig> list = pointConfigService.selectPointConfigList(pointConfig);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(pointConfigService.selectPointConfigById(id));
}
@Log(title = "点位配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsPointConfig pointConfig) {
return toAjax(pointConfigService.insertPointConfig(pointConfig, getUsername()));
}
@Log(title = "点位配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsPointConfig pointConfig) {
return toAjax(pointConfigService.updatePointConfig(pointConfig, getUsername()));
}
@Log(title = "点位配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(pointConfigService.deletePointConfigByIds(ids));
}
@Log(title = "点位配置", businessType = BusinessType.IMPORT)
@PostMapping("/importTemplateBySite")
public AjaxResult importTemplateBySite(@Valid @RequestBody ImportPointTemplateRequest request) {
return success(pointConfigService.importTemplateBySite(request, getUsername()));
}
@Log(title = "点位配置", businessType = BusinessType.IMPORT)
@PostMapping("/importCsv")
public AjaxResult importCsv(@RequestParam String siteId,
@RequestParam(required = false) Boolean overwrite,
@RequestParam("file") MultipartFile file) {
return success(pointConfigService.importCsvBySite(siteId, overwrite, file, getUsername()));
}
@GetMapping("/registerAddress")
public AjaxResult getRegisterAddress(@RequestParam String siteId,
@RequestParam String deviceCategory,
@RequestParam String deviceId,
@RequestParam String dataKey) {
return success(pointConfigService.getRegisterAddress(siteId, deviceCategory, deviceId, dataKey));
}
@PostMapping("/latestValues")
public AjaxResult latestValues(@RequestBody PointConfigLatestValueRequest request) {
return success(pointConfigService.getLatestValues(request));
}
@PostMapping("/curve")
public AjaxResult curve(@RequestBody PointConfigCurveRequest request) {
return success(pointConfigService.getCurveData(request));
}
@Log(title = "点位配置", businessType = BusinessType.INSERT)
@PostMapping("/generateRecent7Days")
public AjaxResult generateRecent7Days(@Valid @RequestBody PointConfigGenerateRecentRequest request) {
return success(pointConfigService.generateRecent7DaysData(request));
}
}

View File

@ -0,0 +1,159 @@
package com.xzzn.web.controller.ems;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
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.DeleteMapping;
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.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsPointMatch;
import com.xzzn.ems.domain.vo.DevicePointMatchExportVo;
import com.xzzn.ems.domain.vo.DevicePointMatchVo;
import com.xzzn.ems.domain.vo.ImportPointDataRequest;
import com.xzzn.ems.domain.vo.ImportPointTemplateRequest;
import com.xzzn.ems.service.IEmsPointMatchService;
import com.xzzn.common.utils.poi.ExcelUtil;
import org.springframework.web.multipart.MultipartFile;
/**
* 点位匹配Controller
*
* @author xzzn
* @date 2025-11-04
*/
@RestController
@RequestMapping("/ems/pointMatch")
public class EmsPointMatchController extends BaseController
{
@Autowired
private IEmsPointMatchService emsPointMatchService;
/**
* 导出点位匹配列表
*/
@PreAuthorize("@ss.hasPermi('system:match:export')")
@Log(title = "点位匹配", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, EmsPointMatch emsPointMatch)
{
List<DevicePointMatchExportVo> list = emsPointMatchService.selectEmsPointMatchList(emsPointMatch);
ExcelUtil<DevicePointMatchExportVo> util = new ExcelUtil<>(DevicePointMatchExportVo.class);
util.exportExcel(response, list, "点位匹配数据");
}
/**
* 查询点位配置列表
*/
@GetMapping("/list")
public com.xzzn.common.core.page.TableDataInfo list(EmsPointMatch emsPointMatch)
{
startPage();
List<EmsPointMatch> list = emsPointMatchService.selectPointMatchConfigList(emsPointMatch);
return getDataTable(list);
}
/**
* 查询点位配置详情
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(emsPointMatchService.selectPointMatchById(id));
}
/**
* 新增点位配置
*/
@Log(title = "点位配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsPointMatch emsPointMatch)
{
emsPointMatch.setCreateBy(getUsername());
return toAjax(emsPointMatchService.insertPointMatch(emsPointMatch));
}
/**
* 修改点位配置
*/
@Log(title = "点位配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsPointMatch emsPointMatch)
{
emsPointMatch.setUpdateBy(getUsername());
return toAjax(emsPointMatchService.updatePointMatch(emsPointMatch));
}
/**
* 删除点位配置
*/
@Log(title = "点位配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(emsPointMatchService.deletePointMatchByIds(ids));
}
/**
* 上传点位清单
* @param file
* @param updateSupport
* @return
* @throws Exception
*/
@PreAuthorize("@ss.hasPermi('system:user:import')")
@Log(title = "点位匹配", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{
ExcelUtil<EmsPointMatch> util = new ExcelUtil<EmsPointMatch>(EmsPointMatch.class);
List<EmsPointMatch> pointMatcheList = util.importExcel(file.getInputStream());
String operName = getUsername();
String message = emsPointMatchService.importPoint(pointMatcheList, updateSupport, operName);
return success(message);
}
/**
* 上传设备的点位清单
* @param request
* @return
* @throws Exception
*/
@PreAuthorize("@ss.hasPermi('system:user:import')")
@Log(title = "点位匹配", businessType = BusinessType.IMPORT)
@PostMapping("/importDataByDevice")
public void importDataByDevice(@Valid ImportPointDataRequest request, HttpServletResponse response) {
List<DevicePointMatchVo> list = emsPointMatchService.importDataByDevice(request, getUsername());
if (CollectionUtils.isNotEmpty(list)) {
ExcelUtil<DevicePointMatchVo> util = new ExcelUtil<>(DevicePointMatchVo.class);
util.exportExcel(response, list, "点位匹配数据");
}
}
/**
* 根据站点导入模板点位配置
* @param request 请求参数
* @return 导入结果
*/
@PreAuthorize("@ss.hasPermi('system:user:import')")
@Log(title = "点位配置", businessType = BusinessType.IMPORT)
@PostMapping("/importTemplateBySite")
public AjaxResult importTemplateBySite(@Valid @RequestBody ImportPointTemplateRequest request)
{
String message = emsPointMatchService.importTemplateBySite(request, getUsername());
return success(message);
}
}

View File

@ -0,0 +1,221 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.config.RuoYiConfig;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.common.utils.file.FileUploadUtils;
import com.xzzn.common.utils.file.MimeTypeUtils;
import com.xzzn.common.utils.poi.ExcelUtil;
import com.xzzn.ems.domain.EmsSiteSetting;
import com.xzzn.ems.domain.vo.DeviceUpdateRequest;
import com.xzzn.ems.domain.vo.DevicesSettingVo;
import com.xzzn.ems.domain.vo.PointDataRequest;
import com.xzzn.ems.domain.vo.PointQueryResponse;
import com.xzzn.ems.domain.vo.SiteDeviceListVo;
import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest;
import com.xzzn.ems.domain.vo.SingleBatteryConfigInitRequest;
import com.xzzn.ems.domain.vo.SingleBatteryConfigInitResultVo;
import com.xzzn.ems.domain.vo.SingleBatteryMonitorImportResultVo;
import com.xzzn.ems.domain.vo.SingleBatteryMonitorImportRowVo;
import com.xzzn.ems.domain.vo.WorkStatusEnumMappingSaveRequest;
import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsSiteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* 绔欑偣閰嶇疆
*/
@RestController
@RequestMapping("/ems/siteConfig")
public class EmsSiteConfigController extends BaseController {
@Autowired
private IEmsSiteService iEmsSiteService;
@Autowired
private IEmsDeviceSettingService iEmsDeviceSettingService;
@GetMapping("/getSiteInfoList")
public TableDataInfo getSiteInfoList(@RequestParam String siteName, @RequestParam String startTime, @RequestParam String endTime) {
startPage();
List<EmsSiteSetting> list = iEmsSiteService.getAllSiteInfoList(siteName, startTime, endTime);
return getDataTable(list);
}
@PostMapping("/addSite")
public AjaxResult addSite(@RequestBody EmsSiteSetting emsSiteSetting) {
emsSiteSetting.setCreateBy(getUsername());
return toAjax(iEmsSiteService.addSite(emsSiteSetting));
}
@PostMapping("/updateSite")
public AjaxResult updateSite(@RequestBody EmsSiteSetting emsSiteSetting) {
emsSiteSetting.setUpdateBy(getUsername());
return toAjax(iEmsSiteService.updateSite(emsSiteSetting));
}
@GetMapping("/getDeviceInfoList")
public TableDataInfo getDeviceInfoList(@RequestParam(value = "siteId", required = false) String siteId,
@RequestParam(value = "deviceCategory", required = false) String deviceCategory) {
startPage();
List<SiteDeviceListVo> list = iEmsSiteService.getAllDeviceListNoDisp(siteId, deviceCategory);
return getDataTable(list);
}
@GetMapping("/getDeviceDetailInfo")
public AjaxResult getDeviceDetailInfo(@RequestParam Long id) {
return success(iEmsDeviceSettingService.getDeviceDetailInfo(id));
}
@GetMapping("/getDeviceList")
public AjaxResult getDeviceInfoList2(@RequestParam String siteId) {
return success(iEmsSiteService.getAllDeviceList(siteId, null));
}
@GetMapping("/getDeviceCategory")
public AjaxResult getDeviceCategory() {
return success(iEmsDeviceSettingService.getDeviceCategory());
}
@PostMapping("/addDevice")
public AjaxResult addDevice(@RequestBody DevicesSettingVo devicesSetting) {
int result = iEmsDeviceSettingService.addDevice(devicesSetting);
if (result > 0) {
return AjaxResult.success(result);
}
return AjaxResult.error("璇ヨ澶囧凡瀛樺湪");
}
@PostMapping("/uploadDeviceImg")
public AjaxResult uploadDeviceImg(@RequestParam("avatarfile") MultipartFile file) throws Exception {
if (!file.isEmpty()) {
String avatar = FileUploadUtils.upload(RuoYiConfig.getDevicePath(), file, MimeTypeUtils.IMAGE_EXTENSION);
AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", avatar);
return ajax;
}
return error("涓婁紶鍥剧墖寮傚父锛岃鑱旂郴绠$悊鍛?");
}
@PostMapping("/updateDevice")
public AjaxResult updateDevice(@RequestBody DevicesSettingVo emsDevicesSetting) {
int result = iEmsDeviceSettingService.updateDevice(emsDevicesSetting);
if (result > 0) {
return AjaxResult.success(result);
} else if (result == -1) {
return AjaxResult.error("鏁版嵁涓嶅瓨鍦?");
} else if (result == -2) {
return AjaxResult.error("璇ヨ澶囧凡瀛樺湪");
}
return AjaxResult.success(result);
}
@DeleteMapping("/deleteService/{id}")
public AjaxResult deleteService(@PathVariable Long id) {
return toAjax(iEmsDeviceSettingService.deleteEmsDevicesSettingById(id));
}
@GetMapping("/getDevicePointList")
public TableDataInfo getDevicePointList(@Validated PointDataRequest request) {
List<PointQueryResponse> result = iEmsDeviceSettingService.getSingleSiteDevicePoints(request);
return getDataTable2(result);
}
@GetMapping("/getSiteAllDeviceCategory")
public AjaxResult getSiteAllDeviceCategory(String siteId) {
return success(iEmsDeviceSettingService.getSiteAllDeviceCategory(siteId));
}
@GetMapping("/getParentDeviceId")
public AjaxResult getParentDeviceId(@RequestParam String siteId, @RequestParam String deviceCategory) {
return success(iEmsSiteService.getParentCategoryDeviceId(siteId, deviceCategory));
}
@GetMapping("/getDeviceListBySiteAndCategory")
public AjaxResult getDeviceListBySiteAndCategory(String siteId, String deviceCategory) {
return success(iEmsDeviceSettingService.getDeviceListBySiteAndCategory(siteId, deviceCategory));
}
@GetMapping("/getSingleMonitorProjectPointMapping")
public AjaxResult getSingleMonitorProjectPointMapping(@RequestParam String siteId) {
return success(iEmsDeviceSettingService.getSiteMonitorProjectPointMapping(siteId));
}
@PostMapping("/saveSingleMonitorProjectPointMapping")
public AjaxResult saveSingleMonitorProjectPointMapping(@RequestBody SiteMonitorProjectPointMappingSaveRequest request) {
int rows = iEmsDeviceSettingService.saveSiteMonitorProjectPointMapping(request, getUsername());
return AjaxResult.success(rows);
}
@GetMapping("/getSingleMonitorWorkStatusEnumMappings")
public AjaxResult getSingleMonitorWorkStatusEnumMappings(@RequestParam String siteId) {
return success(iEmsDeviceSettingService.getSiteWorkStatusEnumMappings(siteId));
}
@PostMapping("/saveSingleMonitorWorkStatusEnumMappings")
public AjaxResult saveSingleMonitorWorkStatusEnumMappings(@RequestBody WorkStatusEnumMappingSaveRequest request) {
int rows = iEmsDeviceSettingService.saveSiteWorkStatusEnumMappings(request.getSiteId(), request.getMappings(), getUsername());
return AjaxResult.success(rows);
}
@GetMapping("/downloadSingleBatteryMonitorImportTemplate")
public void downloadSingleBatteryMonitorImportTemplate(HttpServletResponse response) {
ExcelUtil<SingleBatteryMonitorImportRowVo> util = new ExcelUtil<>(SingleBatteryMonitorImportRowVo.class);
util.importTemplateExcel(response, "单体电池导入模板", "单体电池导入模板");
}
@PostMapping("/importSingleBatteryMonitorMappings")
public AjaxResult importSingleBatteryMonitorMappings(@RequestParam("siteId") String siteId,
@RequestParam("file") MultipartFile file) throws Exception {
ExcelUtil<SingleBatteryMonitorImportRowVo> util = new ExcelUtil<>(SingleBatteryMonitorImportRowVo.class);
List<SingleBatteryMonitorImportRowVo> rowList = util.importExcel(file.getInputStream(), 1);
if (rowList == null) {
return AjaxResult.error("导入失败:未读取到任何数据,请检查 Excel 文件内容");
}
List<SingleBatteryMonitorImportRowVo> validRowList = new ArrayList<>();
for (int i = 0; i < rowList.size(); i++) {
SingleBatteryMonitorImportRowVo row = rowList.get(i);
if (row == null) {
continue;
}
row.setRowNum(i + 3);
validRowList.add(row);
}
if (validRowList.isEmpty()) {
return AjaxResult.error("导入失败:未识别到有效数据,请使用系统导出的最新模板,并保持表头名称不变");
}
SingleBatteryMonitorImportResultVo result = iEmsDeviceSettingService.importSingleBatteryMonitorMappings(siteId, validRowList, getUsername());
return AjaxResult.success(result);
}
@PostMapping("/initializeSingleBatteryMonitorMappings")
public AjaxResult initializeSingleBatteryMonitorMappings(@RequestBody SingleBatteryConfigInitRequest request) {
SingleBatteryConfigInitResultVo result = iEmsDeviceSettingService.initializeSingleBatteryMonitorMappings(request, getUsername());
return AjaxResult.success(result);
}
// @PreAuthorize("@ss.hasPermi('system:device:onAndOff')")
@Log(title = "寮€鍏虫満", businessType = BusinessType.UPDATE)
@PostMapping("/updateDeviceStatus")
public AjaxResult updateDeviceStatus(@Valid @RequestBody DeviceUpdateRequest request) {
return success(iEmsDeviceSettingService.updateDeviceStatus(request));
}
}

View File

@ -2,7 +2,7 @@ package com.xzzn.web.controller.ems;
import com.xzzn.common.core.controller.BaseController; import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult; import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.ems.service.IEmsSiteService; import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.service.IHomePageService; import com.xzzn.ems.service.IHomePageService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController;
/** /**
* *
// * 站点地图 * 站点地图
* *
*/ */
@RestController @RestController
@ -26,9 +26,22 @@ public class EmsSiteMapController extends BaseController{
* 获取某个站点基本信息 * 获取某个站点基本信息
*/ */
@GetMapping("/getSingleSiteBaseInfo") @GetMapping("/getSingleSiteBaseInfo")
public AjaxResult getSingleSiteBaseInfo(@RequestParam Long siteId) public AjaxResult getSingleSiteBaseInfo(@RequestParam String siteId)
{ {
return success(homePageService.getSingleSiteBaseInfo(siteId)); return success(homePageService.getSingleSiteBaseInfo(siteId));
} }
/**
* 获取某个站点7天充放电数据
*/
@GetMapping("/getSevenChargeData")
public AjaxResult getSevenChargeData(DateSearchRequest request)
{
String siteId = request.getSiteId();
if(siteId == null || siteId.isEmpty()) {
return error("站点必传");
}
return success(homePageService.getSevenChargeData(request));
}
} }

View File

@ -2,10 +2,27 @@ package com.xzzn.web.controller.ems;
import com.xzzn.common.core.controller.BaseController; import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult; import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.vo.BMSBatteryDataList;
import com.xzzn.ems.domain.vo.BatteryDataStatsListVo;
import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.RunningGraphRequest;
import com.xzzn.ems.domain.vo.SiteBatteryDataList;
import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest;
import com.xzzn.ems.domain.vo.SiteMonitorRuningInfoVo;
import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsSiteService;
import com.xzzn.ems.service.IEmsStatsReportService;
import com.xzzn.ems.service.ISingleSiteService; import com.xzzn.ems.service.ISingleSiteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/** /**
* *
* 单站监控 * 单站监控
@ -14,24 +31,41 @@ import org.springframework.web.bind.annotation.*;
@RestController @RestController
@RequestMapping("/ems/siteMonitor") @RequestMapping("/ems/siteMonitor")
public class EmsSiteMonitorController extends BaseController{ public class EmsSiteMonitorController extends BaseController{
private static final Logger log = LoggerFactory.getLogger(EmsSiteMonitorController.class);
private static final String RUNNING_GRAPH_CTRL_DEBUG = "RunningGraphCtrlDebug";
@Autowired @Autowired
private ISingleSiteService iSingleSiteService; private ISingleSiteService iSingleSiteService;
@Autowired
private IEmsSiteService iEmsSiteService;
@Autowired
private IEmsStatsReportService iemsStatsReportService;
@Autowired
private IEmsDeviceSettingService iEmsDeviceSettingService;
/** /**
* 获取单站首页数据 * 获取单站首页数据
*/ */
@GetMapping("/homeView") @GetMapping("/homeView")
public AjaxResult getSingleSiteViewInfo(@RequestParam Long siteId) public AjaxResult getSingleSiteViewInfo(@RequestParam String siteId)
{ {
return success(iSingleSiteService.getSiteMonitorDataVo(siteId)); return success(iSingleSiteService.getSiteMonitorDataVo(siteId));
} }
/**
* 获取单站首页总累计运行数据(基于日表)
*/
@GetMapping("/homeTotalView")
public AjaxResult getSingleSiteHomeTotalView(@RequestParam String siteId)
{
return success(iSingleSiteService.getSiteMonitorTotalDataVo(siteId));
}
/** /**
* 单站监控-设备监控-实时运行头部数据 * 单站监控-设备监控-实时运行头部数据
*/ */
@GetMapping("/runningHeadInfo") @GetMapping("/runningHeadInfo")
public AjaxResult getRunningHeadInfo(@RequestParam Long siteId) public AjaxResult getRunningHeadInfo(@RequestParam String siteId)
{ {
return success(iSingleSiteService.getSiteRunningHeadInfo(siteId)); return success(iSingleSiteService.getSiteRunningHeadInfo(siteId));
} }
@ -39,17 +73,83 @@ public class EmsSiteMonitorController extends BaseController{
/** /**
* 单站监控-设备监控-实时运行曲线图数据 * 单站监控-设备监控-实时运行曲线图数据
*/ */
@GetMapping("/runningGraph") @GetMapping("/runningGraph/storagePower")
public AjaxResult getRunningGraph(@RequestParam Long siteId) public AjaxResult getRunningGraphStorage(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraph(siteId)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphStorage(request);
int deviceCount = data == null || data.getPcsPowerList() == null ? 0 : data.getPcsPowerList().size();
log.info("{} storage, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, deviceCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
deviceCount);
return success(data);
}
@GetMapping("/runningGraph/pcsMaxTemp")
public AjaxResult getRunningGraphPcsMaxTemp(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{
SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphPcsMaxTemp(request);
int deviceCount = data == null || data.getPcsMaxTempList() == null ? 0 : data.getPcsMaxTempList().size();
log.info("{} pcsMaxTemp, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, deviceCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
deviceCount);
return success(data);
}
@GetMapping("/runningGraph/batteryAveSoc")
public AjaxResult getRunningGraphBatterySoc(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{
SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphBatterySoc(request);
int pointCount = data == null || data.getBatteryAveSOCList() == null ? 0 : data.getBatteryAveSOCList().size();
log.info("{} batteryAveSoc, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, pointCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
pointCount);
return success(data);
}
@GetMapping("/runningGraph/batteryAveTemp")
public AjaxResult getRunningGraphBatteryTemp(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{
SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphBatteryTemp(request);
int pointCount = data == null || data.getBatteryAveTempList() == null ? 0 : data.getBatteryAveTempList().size();
log.info("{} batteryAveTemp, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, pointCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
pointCount);
return success(data);
} }
/** /**
* 单站监控-设备监控-PCS * 单站监控-设备监控-PCS
*/ */
@GetMapping("/getPcsDetailInfo") @GetMapping("/getPcsDetailInfo")
public AjaxResult getPcsDetailInfo(@RequestParam Long siteId) public AjaxResult getPcsDetailInfo(@RequestParam String siteId)
{ {
return success(iSingleSiteService.getPcsDetailInfo(siteId)); return success(iSingleSiteService.getPcsDetailInfo(siteId));
} }
@ -58,7 +158,7 @@ public class EmsSiteMonitorController extends BaseController{
* 单站监控-设备监控-BMS总览 * 单站监控-设备监控-BMS总览
*/ */
@GetMapping("/getBMSOverView") @GetMapping("/getBMSOverView")
public AjaxResult getBMSOverView(@RequestParam Long siteId) public AjaxResult getBMSOverView(@RequestParam String siteId)
{ {
return success(iSingleSiteService.getBMSOverView(siteId)); return success(iSingleSiteService.getBMSOverView(siteId));
} }
@ -67,7 +167,7 @@ public class EmsSiteMonitorController extends BaseController{
* 单站监控-设备监控-BMS电池簇 * 单站监控-设备监控-BMS电池簇
*/ */
@GetMapping("/getBMSBatteryCluster") @GetMapping("/getBMSBatteryCluster")
public AjaxResult getBMSBatteryCluster(@RequestParam Long siteId) public AjaxResult getBMSBatteryCluster(@RequestParam String siteId)
{ {
return success(iSingleSiteService.getBMSBatteryCluster(siteId)); return success(iSingleSiteService.getBMSBatteryCluster(siteId));
} }
@ -76,26 +176,147 @@ public class EmsSiteMonitorController extends BaseController{
* 获取所有电池堆 * 获取所有电池堆
*/ */
@GetMapping("/getStackNameList") @GetMapping("/getStackNameList")
public AjaxResult getStackNameList(@RequestParam Long siteId) public AjaxResult getStackNameList(@RequestParam String siteId)
{ {
return success(iSingleSiteService.getBMSBatteryCluster(siteId)); return success(iEmsSiteService.getAllStackInfo(siteId));
}
/**
* 获取所有pcs
*/
@GetMapping("/getPcsNameList")
public AjaxResult getPcsNameList(@RequestParam String siteId)
{
return success(iEmsSiteService.getAllPcsInfo(siteId));
} }
/** /**
* 获取所有电池簇 * 获取所有电池簇
*/ */
@GetMapping("/getClusterNameList") @GetMapping("/getClusterNameList")
public AjaxResult getClusterNameList(@RequestParam Long siteId) public AjaxResult getClusterNameList(@RequestParam String siteId, @RequestParam String stackDeviceId)
{ {
return success(iSingleSiteService.getBMSBatteryCluster(siteId)); return success(iEmsSiteService.getAllClusterInfo(siteId, stackDeviceId));
} }
/** /**
* 液冷设备参数 * 液冷设备参数
*/ */
@GetMapping("/getCoolingDataList") @GetMapping("/getCoolingDataList")
public AjaxResult getCoolingDataList(@RequestParam Long siteId) public AjaxResult getCoolingDataList(@RequestParam String siteId)
{ {
return success(iSingleSiteService.getCoolingDataList(siteId)); return success(iSingleSiteService.getCoolingDataList(siteId));
} }
/**
* 获取电池簇下面的单体电池数据
*/
@GetMapping("/getClusterDataInfoList")
public TableDataInfo getClusterDataInfoList(@RequestParam String clusterDeviceId,@RequestParam String siteId,
@RequestParam String stackDeviceId,@RequestParam String batteryId)
{
startPage();
SiteBatteryDataList siteBatteryDataList = new SiteBatteryDataList();
// 单体电池数据
List<BatteryDataStatsListVo> List = iSingleSiteService.getClusterDataInfoList(clusterDeviceId,siteId,stackDeviceId,batteryId);
// 对batteryList进行分页处理
List<BatteryDataStatsListVo> batteryList = paginateList(List);
siteBatteryDataList.setBatteryList(batteryList);
// 封装分页信息
TableDataInfo pageInfo = new TableDataInfo();
pageInfo.setTotal(List.size());
pageInfo.setRows(Arrays.asList(siteBatteryDataList));
pageInfo.setCode(0);
pageInfo.setMsg("查询成功");
return pageInfo;
}
/**
* 单个单体电池曲线图
*/
@GetMapping("/getSingleBatteryData")
public AjaxResult getSingleBatteryData(DateSearchRequest requestVo)
{
return success(iemsStatsReportService.getSingleBatteryData(requestVo));
}
/**
* 电表数据
*/
@GetMapping("/getAmmeterDataList")
public AjaxResult getAmmeterDataList(@RequestParam String siteId)
{
return success(iSingleSiteService.getAmmeterDataList(siteId));
}
/**
* 动环数据
*/
@GetMapping("/getDhDataList")
public AjaxResult getDhDataList(@RequestParam String siteId)
{
return success(iSingleSiteService.getDhDataList(siteId));
}
/**
* 消防数据
*/
@GetMapping("/getXfDataList")
public AjaxResult getXfDataList(@RequestParam String siteId)
{
return success(iSingleSiteService.getXfDataList(siteId));
}
/**
* EMS数据
*/
@GetMapping("/getEmsDataList")
public AjaxResult getEmsDataList(@RequestParam String siteId)
{
return success(iSingleSiteService.getEmsDataList(siteId));
}
/**
* 单站监控-首页-点位展示
* 储能功率、电网功率、负荷功率、光伏功率
* SOC、SOH、电池平均温度
*/
@GetMapping("/getPointData")
public AjaxResult getPointData(DateSearchRequest requestVo)
{
if (!StringUtils.isEmpty(requestVo.getSiteId())) {
return success(iSingleSiteService.getPointData(requestVo));
} else {
return error("缺少必传项");
}
}
/**
* 单站监控项目点位配置查询
*/
@GetMapping("/getProjectPointMapping")
public AjaxResult getProjectPointMapping(@RequestParam String siteId)
{
return success(iEmsDeviceSettingService.getSiteMonitorProjectPointMapping(siteId));
}
/**
* 单站监控项目展示数据查询(配置字段 + 字段值)
*/
@GetMapping("/getProjectDisplayData")
public AjaxResult getProjectDisplayData(@RequestParam String siteId)
{
return success(iEmsDeviceSettingService.getSiteMonitorProjectDisplay(siteId));
}
/**
* 单站监控项目展示数据写入
*/
@PostMapping("/saveProjectDisplayData")
public AjaxResult saveProjectDisplayData(@RequestBody SiteMonitorDataSaveRequest request)
{
return AjaxResult.success(iEmsDeviceSettingService.saveSiteMonitorProjectData(request, getUsername()));
}
} }

View File

@ -0,0 +1,220 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.vo.AmmeterRevenueStatisListVo;
import com.xzzn.ems.domain.vo.AmmeterStatisListVo;
import com.xzzn.ems.domain.vo.ClusterStatisListVo;
import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.StatisAmmeterDateRequest;
import com.xzzn.ems.domain.vo.StatisClusterDateRequest;
import com.xzzn.ems.domain.vo.WeatherSyncResultVo;
import com.xzzn.ems.service.IEmsStatsReportService;
import com.xzzn.ems.service.IEmsWeatherSyncService;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
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.RestController;
/**
* 单站监控-统计报表
*
* @author xzzn
*/
@RestController
@RequestMapping("/ems/statsReport")
public class EmsStatisticalReportController extends BaseController
{
@Autowired
private IEmsStatsReportService ieEmsStatsReportService;
@Autowired
private IEmsWeatherSyncService iEmsWeatherSyncService;
/**
* 概率统计-收益指标查询
*/
@GetMapping("/getRevenueData")
public AjaxResult getRevenueData(DateSearchRequest requestVo)
{
return success(null);
}
/**
* 概率统计-电量指标查询
*/
@GetMapping("/getElectricData")
public AjaxResult getElectricData(DateSearchRequest requestVo)
{
if (!StringUtils.isEmpty(requestVo.getSiteId())) {
return success(ieEmsStatsReportService.getElectricDataResult(requestVo));
} else {
return error("站点id必传");
}
}
/**
* 概率统计-PCS曲线
*/
@GetMapping("/getPCSData")
public AjaxResult getPCSData(DateSearchRequest requestVo)
{
if (!StringUtils.isEmpty(requestVo.getSiteId())&&
!StringUtils.isEmpty(requestVo.getDataType())) {
return success(ieEmsStatsReportService.getPCSDataResult(requestVo));
} else {
return error("缺少必传项");
}
}
/**
* 概率统计-电池堆曲线
*/
@GetMapping("/getStackData")
public AjaxResult getStackData(DateSearchRequest requestVo)
{
if (!StringUtils.isEmpty(requestVo.getSiteId()) &&
!StringUtils.isEmpty(requestVo.getDataType())) {
return success(ieEmsStatsReportService.getStackDataResult(requestVo));
} else {
return error("缺少必传项");
}
}
/**
* 概率统计-电池温度等数据
*/
@GetMapping("/getClusterData")
public TableDataInfo getClusterData(StatisClusterDateRequest requestVo)
{
startPage();
List<ClusterStatisListVo> dataList = new ArrayList<>();
if (!StringUtils.isEmpty(requestVo.getStackId()) &&
!StringUtils.isEmpty(requestVo.getClusterId())) {
dataList = ieEmsStatsReportService.getClusterDataResult(requestVo);
return getDataTable(dataList);
} else {
return getDataTable(dataList);
}
}
/**
* 概率统计-获取站点下所有电表
*/
@GetMapping("/getLoadNameList")
public AjaxResult getLoadNameList(String siteId)
{
if (!StringUtils.isEmpty(siteId)) {
return success(ieEmsStatsReportService.getLoadNameList(siteId));
} else {
return error("缺少必传项");
}
}
/**
* 概率统计-电表报表
*/
@GetMapping("/getAmmeterData")
public TableDataInfo getAmmeterData(StatisAmmeterDateRequest requestVo)
{
startPage();
List<AmmeterStatisListVo> dataList = ieEmsStatsReportService.getAmmeterDataResult(requestVo);
return getDataTable(dataList);
}
/**
* 统计报表-电表报表(直接基于 ems_daily_energy_data
*/
@GetMapping("/getAmmeterDataFromDaily")
public TableDataInfo getAmmeterDataFromDaily(StatisAmmeterDateRequest requestVo)
{
startPage();
List<AmmeterStatisListVo> dataList = ieEmsStatsReportService.getAmmeterDataResult(requestVo);
return getDataTable(dataList);
}
/**
* 导出电表报表
*/
@PreAuthorize("@ss.hasPermi('system:ammeterData:export')")
@Log(title = "电表报表", businessType = BusinessType.EXPORT)
@PostMapping("/exportAmmeterData")
public void exportAmmeterData(HttpServletResponse response, StatisAmmeterDateRequest requestVo)
{
ieEmsStatsReportService.exportAmmeterData(response, requestVo);
}
/**
* 导出电表报表(直接基于 ems_daily_energy_data
*/
@PreAuthorize("@ss.hasPermi('system:ammeterData:export')")
@Log(title = "电表报表", businessType = BusinessType.EXPORT)
@PostMapping("/exportAmmeterDataFromDaily")
public void exportAmmeterDataFromDaily(HttpServletResponse response, StatisAmmeterDateRequest requestVo)
{
ieEmsStatsReportService.exportAmmeterData(response, requestVo);
}
/**
* 概率统计-电表收益报表
*/
@GetMapping("/getAmmeterRevenueData")
public TableDataInfo getAmmeterRevenueData(StatisAmmeterDateRequest requestVo)
{
startPage();
List<AmmeterRevenueStatisListVo> dataList = ieEmsStatsReportService.getAmmeterRevenueDataResult(requestVo);
return getDataTable(dataList);
}
/**
* 导出收益报表
*/
@PreAuthorize("@ss.hasPermi('system:ammeterRevenueData:export')")
@Log(title = "收益报表", businessType = BusinessType.EXPORT)
@PostMapping("/exportAmmeterRevenueData")
public void exportAmmeterRevenueData(HttpServletResponse response, StatisAmmeterDateRequest requestVo)
{
ieEmsStatsReportService.exportAmmeterRevenueData(response, requestVo);
}
/**
* 概率统计-功率曲线
*/
@GetMapping("/getPowerData")
public AjaxResult getPowerData(DateSearchRequest requestVo)
{
if (!StringUtils.isEmpty(requestVo.getSiteId())) {
return success(ieEmsStatsReportService.getPowerDataList(requestVo));
} else {
return error("缺少必传项");
}
}
/**
* 手动触发天气同步
*/
@PostMapping("/syncWeatherByDateRange")
public AjaxResult syncWeatherByDateRange(StatisAmmeterDateRequest requestVo)
{
if (StringUtils.isEmpty(requestVo.getSiteId())
|| StringUtils.isEmpty(requestVo.getStartTime())
|| StringUtils.isEmpty(requestVo.getEndTime())) {
return error("缺少必传项: siteId/startTime/endTime");
}
WeatherSyncResultVo resultVo = iEmsWeatherSyncService.syncWeatherByDateRange(requestVo);
return success(resultVo);
}
}

View File

@ -0,0 +1,76 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsStrategyRunning;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.ems.service.IEmsStrategyService;
/**
* 策略Controller
*
* @author xzzn
* @date 2025-07-10
*/
@RestController
@RequestMapping("/system/strategyRunning")
public class EmsStrategyController extends BaseController
{
@Autowired
private IEmsStrategyService emsStrategyService;
/**
* 获取站点策略配置
*/
@GetMapping("/list")
public AjaxResult list(String siteId) {
return success(emsStrategyService.selectEmsStrategyRunningList(siteId));
}
/**
* 停止策略
*/
@GetMapping(value = "/stop")
public AjaxResult stop(Long id)
{
return toAjax(emsStrategyService.stopRunningStrategy(id));
}
/**
* 获取主策略列表
*/
@GetMapping(value = "/getMainStrategyList")
public AjaxResult getMainStrategyList()
{
return success(emsStrategyService.getMainStrategyList());
}
/**
* 获取辅助策略列表
*/
@GetMapping(value = "/getAuxStrategyList")
public AjaxResult getAuxStrategyList()
{
return success(emsStrategyService.getAuxStrategyList());
}
/**
* 配置策略
*/
@PostMapping(value = "/configStrategy")
public AjaxResult configStrategy(@RequestBody EmsStrategyRunning emsStrategyRunning)
{
if (emsStrategyRunning.getMainStrategyId() == null
|| StringUtils.isEmpty(emsStrategyRunning.getSiteId())){
return error("缺少必填字段");
}
emsStrategyRunning.setCreateBy(getUsername());
emsStrategyRunning.setUpdateBy(getUsername());
int result = emsStrategyService.configStrategy(emsStrategyRunning);
if (result == -1){
return error("不支持重复添加");
}
return success(result);
}
}

View File

@ -0,0 +1,113 @@
package com.xzzn.web.controller.ems;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.EmsStrategyTimeConfig;
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.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsStrategyCurve;
import com.xzzn.ems.service.IEmsStrategyCurveService;
import com.xzzn.common.utils.poi.ExcelUtil;
/**
* 策曲线Controller
*
* @author xzzn
* @date 2025-07-11
*/
@RestController
@RequestMapping("/strategy/curve")
public class EmsStrategyCurveController extends BaseController
{
@Autowired
private IEmsStrategyCurveService emsStrategyCurveService;
/**
* 查询策曲线列表
*/
@PreAuthorize("@ss.hasPermi('system:curve:list')")
@GetMapping("/list")
public AjaxResult list(EmsStrategyCurve emsStrategyCurve)
{
return success(emsStrategyCurveService.selectEmsStrategyCurveList(emsStrategyCurve));
}
/**
* 导出策曲线列表
*/
@PreAuthorize("@ss.hasPermi('system:curve:export')")
@Log(title = "策曲线", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, EmsStrategyCurve emsStrategyCurve)
{
List<EmsStrategyCurve> list = emsStrategyCurveService.selectEmsStrategyCurveList(emsStrategyCurve);
ExcelUtil<EmsStrategyCurve> util = new ExcelUtil<EmsStrategyCurve>(EmsStrategyCurve.class);
util.exportExcel(response, list, "策曲线数据");
}
/**
* 获取策曲线详细信息
*/
@PreAuthorize("@ss.hasPermi('system:curve:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(emsStrategyCurveService.selectEmsStrategyCurveById(id));
}
/**
* 新增策曲线
*/
@PreAuthorize("@ss.hasPermi('system:curve:add')")
@Log(title = "策曲线", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsStrategyCurve emsStrategyCurve)
{
return toAjax(emsStrategyCurveService.insertEmsStrategyCurve(emsStrategyCurve));
}
/**
* 修改策曲线
*/
@PreAuthorize("@ss.hasPermi('system:curve:edit')")
@Log(title = "策曲线", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsStrategyCurve emsStrategyCurve)
{
return toAjax(emsStrategyCurveService.updateEmsStrategyCurve(emsStrategyCurve));
}
/**
* 删除策曲线
*/
@PreAuthorize("@ss.hasPermi('system:curve:remove')")
@Log(title = "策曲线", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(emsStrategyCurveService.deleteEmsStrategyCurveByIds(ids));
}
/**
* 获取策略曲线图数据
*/
@GetMapping("/curveList")
public AjaxResult getCurveList(EmsStrategyTemp tempConfig)
{
return success(emsStrategyCurveService.getStrategyCurveList(tempConfig));
}
}

View File

@ -0,0 +1,48 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
import com.xzzn.ems.service.IEmsStrategyRuntimeConfigService;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 策略运行参数配置Controller
*/
@RestController
@RequestMapping("/system/strategyRuntimeConfig")
public class EmsStrategyRuntimeConfigController extends BaseController {
@Autowired
private IEmsStrategyRuntimeConfigService runtimeConfigService;
/**
* 按站点ID获取策略运行参数
*/
@GetMapping("/getBySiteId")
public AjaxResult getBySiteId(String siteId) {
if (StringUtils.isEmpty(siteId)) {
return error("缺少必填字段siteId");
}
return success(runtimeConfigService.getBySiteId(siteId));
}
/**
* 保存策略运行参数按siteId新增/更新)
*/
@PostMapping("/save")
public AjaxResult save(@RequestBody EmsStrategyRuntimeConfig config) {
if (config == null || StringUtils.isEmpty(config.getSiteId())) {
return error("缺少必填字段siteId");
}
config.setCreateBy(getUsername());
config.setUpdateBy(getUsername());
return toAjax(runtimeConfigService.saveBySiteId(config));
}
}

View File

@ -0,0 +1,94 @@
package com.xzzn.web.controller.ems;
import com.xzzn.ems.domain.vo.StrategyTempConfigRequest;
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.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.service.IEmsStrategyTempService;
/**
* 模板Controller
*
* @author xzzn
* @date 2025-07-11
*/
@RestController
@RequestMapping("/strategy/temp")
public class EmsStrategyTempController extends BaseController
{
@Autowired
private IEmsStrategyTempService emsStrategyTempService;
/**
* 根据策略id站点id获取所有模板名称
*/
@GetMapping("/getTempNameList")
public AjaxResult getTempNameList(Long strategyId, String siteId)
{
return success(emsStrategyTempService.getTempNameList(strategyId, siteId));
}
/**
* 获取单个模板时间配置详细信息
*/
@PreAuthorize("@ss.hasPermi('system:temp:list')")
@GetMapping("/list")
public AjaxResult list(String templateId)
{
return success(emsStrategyTempService.selectEmsStrategyTempList(templateId));
}
/**
* 新增模板及时间配置
*/
@PreAuthorize("@ss.hasPermi('system:temp:add')")
@Log(title = "模板", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody StrategyTempConfigRequest requestVo)
{
boolean result = emsStrategyTempService.addNewTempAndTimeConfig(requestVo);
if (result) {
return success();
} else {
return AjaxResult.error("新增失败请重试!");
}
}
/**
* 修改模板
*/
@PreAuthorize("@ss.hasPermi('system:temp:edit')")
@Log(title = "模板", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody StrategyTempConfigRequest requestVo)
{
boolean result = emsStrategyTempService.updateEmsStrategyTemp(requestVo);
if (result) {
return success();
} else {
return AjaxResult.error("更新失败,请重试!");
}
}
/**
* 删除模板
*/
@PreAuthorize("@ss.hasPermi('system:temp:remove')")
@Log(title = "模板", businessType = BusinessType.DELETE)
@DeleteMapping("/{templateId}")
public AjaxResult remove(@PathVariable String templateId)
{
return success(emsStrategyTempService.deleteStrategyTempById(templateId));
}
}

View File

@ -0,0 +1,93 @@
package com.xzzn.web.controller.ems;
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.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsStrategyTimeConfig;
import com.xzzn.ems.service.IEmsStrategyTimeConfigService;
/**
* 时间配置Controller
*
* @author xzzn
* @date 2025-07-11
*/
@RestController
@RequestMapping("/strategy/timeConfig")
public class EmsStrategyTimeConfigController extends BaseController
{
@Autowired
private IEmsStrategyTimeConfigService emsStrategyTimeConfigService;
/**
* 查询时间配置列表
*/
@PreAuthorize("@ss.hasPermi('system:config:list')")
@GetMapping("/list")
public AjaxResult list(EmsStrategyTimeConfig emsStrategyTimeConfig)
{
return success(emsStrategyTimeConfigService.getStrategyTimeList(emsStrategyTimeConfig));
}
/**
* 获取时间配置详细信息
*/
@PreAuthorize("@ss.hasPermi('system:config:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(emsStrategyTimeConfigService.selectEmsStrategyTimeConfigById(id));
}
/**
* 新增时间配置
*/
@PreAuthorize("@ss.hasPermi('system:config:add')")
@Log(title = "时间配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody List<EmsStrategyTimeConfig> emsStrategyTimeConfigList)
{
boolean result = emsStrategyTimeConfigService.insertEmsStrategyTimeConfig(emsStrategyTimeConfigList);
if (result){
return success();
}else {
return error("编辑失败请重试!");
}
}
/**
* 修改时间配置
*/
@PreAuthorize("@ss.hasPermi('system:config:edit')")
@Log(title = "时间配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsStrategyTimeConfig emsStrategyTimeConfig)
{
return toAjax(emsStrategyTimeConfigService.updateEmsStrategyTimeConfig(emsStrategyTimeConfig));
}
/**
* 删除时间配置
*/
@PreAuthorize("@ss.hasPermi('system:config:remove')")
@Log(title = "时间配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(emsStrategyTimeConfigService.deleteEmsStrategyTimeConfigByIds(ids));
}
}

View File

@ -5,6 +5,7 @@ import javax.servlet.http.HttpServletResponse;
import com.xzzn.common.utils.poi.ExcelUtil; import com.xzzn.common.utils.poi.ExcelUtil;
import com.xzzn.ems.domain.EmsTicket; import com.xzzn.ems.domain.EmsTicket;
import com.xzzn.ems.domain.vo.TicketListVo;
import com.xzzn.ems.service.IEmsTicketService; import com.xzzn.ems.service.IEmsTicketService;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -40,10 +41,10 @@ public class EmsTicketController extends BaseController
*/ */
@PreAuthorize("@ss.hasPermi('system:ticket:list')") @PreAuthorize("@ss.hasPermi('system:ticket:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(EmsTicket emsTicket) public TableDataInfo list(Long[] status)
{ {
startPage(); startPage();
List<EmsTicket> list = emsTicketService.selectEmsTicketList(emsTicket); List<TicketListVo> list = emsTicketService.getAllTicketList(status);
return getDataTable(list); return getDataTable(list);
} }
@ -102,4 +103,15 @@ public class EmsTicketController extends BaseController
{ {
return toAjax(emsTicketService.deleteEmsTicketByIds(ids)); return toAjax(emsTicketService.deleteEmsTicketByIds(ids));
} }
/**
* 废弃工单
*/
@PreAuthorize("@ss.hasPermi('system:ticket:delete')")
@Log(title = "工单主", businessType = BusinessType.DELETE)
@PostMapping("/drop")
public AjaxResult drop(@RequestBody EmsTicket emsTicket)
{
return toAjax(emsTicketService.dropEmsTicketById(emsTicket.getId()));
}
} }

View File

@ -0,0 +1,251 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.constant.RedisKeyConstants;
import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.TopicHandleType;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsMqttTopicConfig;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.service.IDeviceDataProcessService;
import com.xzzn.ems.service.IMqttSyncLogService;
import com.xzzn.framework.manager.MqttLifecycleManager;
import com.xzzn.framework.web.service.MqttPublisher;
import com.xzzn.framework.web.service.MqttSubscriber;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.commons.collections4.CollectionUtils;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
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.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MqttMessageController implements MqttPublisher, MqttSubscriber {
private static final Logger log = LoggerFactory.getLogger(MqttMessageController.class);
private final MqttLifecycleManager mqttLifecycleManager;
@Autowired
private IDeviceDataProcessService deviceDataProcessService;
@Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
@Autowired
private IMqttSyncLogService iMqttSyncLogService;
@Autowired
private RedisCache redisCache;
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Autowired
public MqttMessageController(MqttLifecycleManager mqttLifecycleManager) {
this.mqttLifecycleManager = mqttLifecycleManager;
}
@PostConstruct
public void init() {
List<EmsMqttTopicConfig> topicConfigList = emsMqttTopicConfigMapper.selectEmsMqttTopicConfigList(null);
if (CollectionUtils.isEmpty (topicConfigList)) {
log.info ("未查询到任何 MQTT 主题配置,跳过订阅");
return;
}
for (EmsMqttTopicConfig topicConfig : topicConfigList) {
String topic = topicConfig.getMqttTopic();
if (StringUtils.isEmpty(topic)) {
log.info ("主题配置 ID:" +topicConfig.getId() +"的 mqttTopic 为空,跳过订阅");
continue;
}
int qos = topicConfig.getQos() == null ? 1 : topicConfig.getQos();
if (qos < 0 || qos > 2) {
log.info ("主题:" + topic +"的 QoS值"+ qos + "不合法自动调整为1");
qos = 1;
}
IMqttMessageListener listener = getMqttListenerByTopic(topic, topicConfig.getHandleType());
subscribe(topic, qos, listener);
}
// 订阅奉贤系统状态主题
/*subscribe("021_FXX_01_UP", 1, this::handleDeviceData);
subscribe("021_FXX_01_RECALL", 1, this::handleDeviceData);
subscribe("021_FXX_01_DOWN", 1, this::handleDeviceData);
subscribe("021_FXX_01", 1, this::handleSystemStatus);
subscribe("021_FXX_01_ALARM_UP", 1, this::handleAlarmData);
// 订阅电动所系统状态主题
subscribe("021_DDS_01_UP", 1, this::handleDeviceData);
subscribe("021_DDS_01_RECALL", 1, this::handleDeviceData);
subscribe("021_DDS_01_DOWN", 1, this::handleDeviceData);
subscribe("021_DDS_01", 1, this::handleSystemStatus);*/
}
private IMqttMessageListener getMqttListenerByTopic(String topic, String handleType) {
TopicHandleType topicHandleType = TopicHandleType.getEnumByCode(handleType);
switch (topicHandleType) {
case DEVICE:
return this::handleDeviceData;
case DEVICE_ALARM:
return this::handleAlarmData;
case STRATEGY:
if (topic.equals("FAULT_PROTECTION_PLAN_UP")) {
return this::handleFaultProtPlanData;
} else if (topic.equals("FAULT_ALARM_RECORD_UP")) {
return this::handleFaultAlarmData;
} else if (topic.equals("FAULT_PLAN_ISSUE_UP")) {
return this::handleFaultPlanIssueData;
} else if (topic.equals("DEVICE_CHANGE_LOG_UP")) {
return this::handleDeviceChangeLogData;
} else {
return this::handleStrategyData;
}
default:
log.warn("Unknown handle type: " + handleType + ", using default handler");
return this::handleSystemStatus;
}
}
// 处理系统状态消息
private void handleSystemStatus(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
System.out.println("[SYSTEM] Status update: " + payload);
}
// 处理设备数据
private void handleDeviceData(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
threadPoolTaskExecutor.execute(() -> {
log.debug("[DEVICE] data: {}", payload);
try {
deviceDataProcessService.handleDeviceData(payload, getSiteIdByTopic(topic));
} catch (Exception e) {
log.error("Failed to process device data message: {}", e.getMessage(), e);
}
});
}
// 处理告警数据
private void handleAlarmData(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
threadPoolTaskExecutor.execute(() -> {
try {
deviceDataProcessService.handleAlarmData(payload, getSiteIdByTopic(topic));
} catch (Exception e) {
log.error("Failed to process device alarm data message: {}", e.getMessage(), e);
}
});
}
private String getSiteIdByTopic(String topic) {
String siteId = redisCache.getCacheObject(RedisKeyConstants.SITE_ID + topic);
if (StringUtils.isEmpty(siteId)) {
EmsMqttTopicConfig topicConfig = emsMqttTopicConfigMapper.selectOneByTopic(topic);
siteId = topicConfig.getSiteId();
redisCache.setCacheObject(RedisKeyConstants.SITE_ID + topic, siteId);
}
return siteId;
}
// 处理运行策略数据:云端-本地
private void handleStrategyData(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
threadPoolTaskExecutor.execute(() -> {
try {
iMqttSyncLogService.handleMqttStrategyData(payload);
} catch (Exception e) {
log.error("Failed to process strategy data message: {}", e.getMessage(), e);
}
});
}
// 处理设备保护告警策略数据:云端-本地
private void handleFaultProtPlanData(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
threadPoolTaskExecutor.execute(() -> {
try {
iMqttSyncLogService.handleMqttPlanData(payload);
} catch (Exception e) {
log.error("Failed to process fault plan data message: {}", e.getMessage(), e);
}
});
}
// 处理保护策略告警信息:本地-云端
private void handleFaultAlarmData(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
threadPoolTaskExecutor.execute(() -> {
try {
iMqttSyncLogService.handleFaultAlarmData(payload);
} catch (Exception e) {
log.error("Failed to process fault plan alarm data message: {}", e.getMessage(), e);
}
});
}
// 处理保护策略下发日志:本地-云端
private void handleFaultPlanIssueData(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
threadPoolTaskExecutor.execute(() -> {
try {
iMqttSyncLogService.handleFaultPlanIssueData(payload);
} catch (Exception e) {
log.error("Failed to process fault plan issue log message: {}", e.getMessage(), e);
}
});
}
// 处理设备状态变更日志:本地-云端
private void handleDeviceChangeLogData(String topic, MqttMessage message) {
String payload = new String(message.getPayload());
threadPoolTaskExecutor.execute(() -> {
try {
iMqttSyncLogService.handleDeviceChangeLogData(payload);
} catch (Exception e) {
log.error("Failed to process device change log message: {}", e.getMessage(), e);
}
});
}
@Override
public void publish(String topic, String message) throws MqttException {
mqttLifecycleManager.publish(topic, message, 0);
}
@Override
public void publish(String topic, String message, int qos) throws MqttException {
mqttLifecycleManager.publish(topic, message, qos);
}
@Override
public void subscribe(String topic, int qos, IMqttMessageListener listener) {
mqttLifecycleManager.subscribe(topic, qos, listener);
}
// 发送设备控制命令
public void sendDeviceCommand(String deviceId, String command) {
try {
String topic = "devices/" + deviceId + "/commands";
publish(topic, command, 1);
} catch (MqttException e) {
System.err.println("Failed to send command to device " + deviceId);
}
}
}

View File

@ -0,0 +1,32 @@
package com.xzzn.web.controller.system;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.ems.domain.vo.BizRemarkBatchGetRequest;
import com.xzzn.ems.domain.vo.BizRemarkSaveRequest;
import com.xzzn.ems.service.ISysBizRemarkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/system/bizRemark")
public class SysBizRemarkController extends BaseController
{
@Autowired
private ISysBizRemarkService sysBizRemarkService;
@PostMapping("/batchGet")
public AjaxResult batchGet(@RequestBody BizRemarkBatchGetRequest request)
{
return success(sysBizRemarkService.getRemarkMap(request.getBizType(), request.getBizKey1(), request.getBizKey2List()));
}
@PostMapping("/save")
public AjaxResult save(@RequestBody BizRemarkSaveRequest request)
{
return toAjax(sysBizRemarkService.saveRemark(request, getUsername()));
}
}

View File

@ -3,6 +3,8 @@ package com.xzzn.web.controller.system;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.xzzn.system.service.impl.SysUserServiceImpl;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@ -52,6 +54,8 @@ public class SysUserController extends BaseController
@Autowired @Autowired
private ISysPostService postService; private ISysPostService postService;
@Autowired
private SysUserServiceImpl sysUserServiceImpl;
/** /**
* 获取用户列表 * 获取用户列表
@ -253,4 +257,13 @@ public class SysUserController extends BaseController
{ {
return success(deptService.selectDeptTreeList(dept)); return success(deptService.selectDeptTreeList(dept));
} }
/**
* 获取所有用户
*/
@GetMapping("/getAllUser")
public AjaxResult getAllUser(SysUser user)
{
return success(userService.selectUserList(user));
}
} }

View File

@ -1,61 +1,61 @@
# 数据源配置 ## 数据源配置
spring: #spring:
datasource: # datasource:
type: com.alibaba.druid.pool.DruidDataSource # type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver # driverClassName: com.mysql.cj.jdbc.Driver
druid: # druid:
# 主库数据源 # # 主库数据源
master: # master:
url: jdbc:mysql://122.51.194.184:13306/setri_ems?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 # url: jdbc:mysql://122.51.194.184:13306/setri_ems?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: ems # username: ems
password: 12345678 # password: 12345678
# 从库数据源 # # 从库数据源
slave: # slave:
# 从数据源开关/默认关闭 # # 从数据源开关/默认关闭
enabled: false # enabled: false
url: # url:
username: # username:
password: # password:
# 初始连接数 # # 初始连接数
initialSize: 5 # initialSize: 5
# 最小连接池数量 # # 最小连接池数量
minIdle: 10 # minIdle: 10
# 最大连接池数量 # # 最大连接池数量
maxActive: 20 # maxActive: 20
# 配置获取连接等待超时的时间 # # 配置获取连接等待超时的时间
maxWait: 60000 # maxWait: 60000
# 配置连接超时时间 # # 配置连接超时时间
connectTimeout: 30000 # connectTimeout: 30000
# 配置网络超时时间 # # 配置网络超时时间
socketTimeout: 60000 # socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 # # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000 # timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒 # # 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000 # minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒 # # 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000 # maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效 # # 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL # validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true # testWhileIdle: true
testOnBorrow: false # testOnBorrow: false
testOnReturn: false # testOnReturn: false
webStatFilter: # webStatFilter:
enabled: true # enabled: true
statViewServlet: # statViewServlet:
enabled: true # enabled: true
# 设置白名单,不填则允许所有访问 # # 设置白名单,不填则允许所有访问
allow: # allow:
url-pattern: /druid/* # url-pattern: /druid/*
# 控制台管理用户名和密码 # # 控制台管理用户名和密码
login-username: xzzn # login-username: xzzn
login-password: 123456 # login-password: 123456
filter: # filter:
stat: # stat:
enabled: true # enabled: true
# 慢SQL记录 # # 慢SQL记录
log-slow-sql: true # log-slow-sql: true
slow-sql-millis: 1000 # slow-sql-millis: 1000
merge-sql: true # merge-sql: true
wall: # wall:
config: # config:
multi-statement-allow: true # multi-statement-allow: true

View File

@ -0,0 +1,214 @@
# 项目相关配置
xzzn:
# 名称
name: EMS
# 版本
version: 0.0.1
# 版权年份
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/xzzn/uploadPathLinux配置 /home/xzzn/uploadPath
profile: ../uploadPath
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
captchaType: math
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8089
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 日志配置
logging:
level:
com.xzzn: info
org.springframework: warn
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间默认10分钟
lockTime: 10
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# redis 配置
redis:
# 地址
host: 127.0.0.1
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: 12345678
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://127.0.0.1:3306/setri_ems?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: ems
password: Aa112211!
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: xzzn
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期默认30分钟
expireTime: 30
# MyBatis配置
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.xzzn.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 请求前缀
pathMapping: /dev-api
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
mqtt:
broker.url: tcp://121.5.164.6:1883
client.id: ems-cloud
username: dmbroker
password: qwer1234
connection-timeout: 15
keep-alive-interval: 30
automatic-reconnect: true
topic: 021_DDS_01_UP
siteId: 021_DDS_01
modbus:
pool:
max-total: 20
max-idle: 10
min-idle: 3
poll:
interval: "0 */5 * * * *" # 5分钟间隔
timeout: 30000 # 30秒超时
weather:
api:
enabled: true
base-url: https://archive-api.open-meteo.com/v1/archive
api-key:
timezone: Asia/Shanghai

View File

@ -0,0 +1,214 @@
# 项目相关配置
xzzn:
# 名称
name: EMS
# 版本
version: 0.0.1
# 版权年份
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/xzzn/uploadPathLinux配置 /home/xzzn/uploadPath
profile: /home/xzzn/uploadPath
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
captchaType: math
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8089
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 日志配置
logging:
level:
com.xzzn: info
org.springframework: warn
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间默认10分钟
lockTime: 10
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# redis 配置
redis:
# 地址
host: 172.17.0.9
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: 12345678
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://172.17.0.13:3306/setri_ems?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: ems
password: Aa112211!
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: xzzn
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期默认30分钟
expireTime: 30
# MyBatis配置
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.xzzn.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 请求前缀
pathMapping: /dev-api
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
mqtt:
broker.url: tcp://121.5.164.6:1883
client.id: ems-cloud
username: dmbroker
password: qwer1234
connection-timeout: 15
keep-alive-interval: 30
automatic-reconnect: true
topic:
siteId:
modbus:
pool:
max-total: 20
max-idle: 10
min-idle: 3
poll:
interval: "0 */5 * * * *" # 5分钟间隔
timeout: 30000 # 30秒超时
weather:
api:
enabled: true
base-url: https://archive-api.open-meteo.com/v1/archive
api-key:
timezone: Asia/Shanghai

View File

@ -34,7 +34,7 @@ server:
# 日志配置 # 日志配置
logging: logging:
level: level:
com.xzzn: debug com.xzzn: info
org.springframework: warn org.springframework: warn
# 用户配置 # 用户配置
@ -87,6 +87,66 @@ spring:
max-active: 8 max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制) # #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms max-wait: -1ms
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://122.51.194.184:13306/setri_ems?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: ems
password: 12345678
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: xzzn
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
# token配置 # token配置
token: token:
@ -127,3 +187,44 @@ xss:
excludes: /system/notice excludes: /system/notice
# 匹配链接 # 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/* urlPatterns: /system/*,/monitor/*,/tool/*
mqtt:
broker.url: tcp://122.51.194.184:1883
client.id: ems-cloud
username: dmbroker
password: qwer1234
connection-timeout: 15
keep-alive-interval: 30
automatic-reconnect: true
topic:
siteId:
influxdb:
enabled: true
url: http://122.51.194.184:8086/
api-token: F2XcmBzZsWcz90ikU2_t7UXY2fzWuf2ruVp1BkusNkIS_gwrQZuiaIjl33XQMQajm7vSI6TQSRnpPSx5CXThlA==
write-method: POST
read-method: GET
write-path: /api/v2/write
query-path: /query
org: ems
bucket: point_data
database: ems_point_data
retention-policy: autogen
measurement: mqtt_point_data
modbus:
pool:
max-total: 20
max-idle: 10
min-idle: 3
poll:
interval: "0 */5 * * * *" # 5分钟间隔
timeout: 30000 # 30秒超时
weather:
api:
enabled: true
base-url: https://archive-api.open-meteo.com/v1/archive
api-key:
timezone: Asia/Shanghai

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<configuration> <configuration>
<!-- 日志存放路径 --> <!-- 日志存放路径 -->
<property name="log.path" value="/etc/xzzn/logs" /> <property name="log.path" value="logs" />
<!-- 日志输出格式 --> <!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" /> <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
@ -90,4 +90,4 @@
<logger name="sys-user" level="info"> <logger name="sys-user" level="info">
<appender-ref ref="sys-user"/> <appender-ref ref="sys-user"/>
</logger> </logger>
</configuration> </configuration>

View File

@ -119,6 +119,12 @@
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
</dependency> </dependency>
<!-- modbus4j (保留兼容) -->
<dependency>
<groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,11 @@
package com.xzzn.common.annotation;
import java.lang.annotation.*;
/**
* 标记调用insert后需要同步数据到其他服务器
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SyncAfterInsert {
}

View File

@ -119,4 +119,12 @@ public class RuoYiConfig
{ {
return getProfile() + "/upload"; return getProfile() + "/upload";
} }
/**
* 获取头像上传路径
*/
public static String getDevicePath()
{
return getProfile() + "/device";
}
} }

View File

@ -170,4 +170,9 @@ public class Constants
*/ */
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.xzzn.common.utils.file", "com.xzzn.common.config", "com.xzzn.generator" }; "org.springframework", "org.apache", "com.xzzn.common.utils.file", "com.xzzn.common.config", "com.xzzn.generator" };
/**
* 昨日充放电最晚数据有效期
*/
public static final Integer DATE_VALID_TIME = 1;
} }

View File

@ -0,0 +1,143 @@
package com.xzzn.common.constant;
/**
* 数据存储 Redis key 常量
*
* @author xzzn
*/
public class RedisKeyConstants
{
/**
* pcs数据 redis key
*/
public static final String PCS = "PCS_";
/**
* pcs branch数据 redis key
*/
public static final String BRANCH = "BRANCH_";
/**
* stack电池堆数据 redis key
*/
public static final String STACK = "STACK_";
/**
* cluster电池簇数据 redis key
*/
public static final String CLUSTER = "CLUSTER_";
/**
* battery单体电池数据 redis key
*/
public static final String BATTERY = "BATTERY_";
/**
* 电表数据 redis key
*/
public static final String AMMETER = "AMMETER_";
/**
* 动环数据 redis key
*/
public static final String DH = "DH_";
/**
* 消防数据 redis key
*/
public static final String XF = "XF_";
/**
* 电池组 redis key
*/
public static final String GROUP = "GROUP_";
/**
* BMSD原始数据 redis key
*/
public static final String ORIGINAL_BMSD = "BMSD_";
/**
* 冷却数据 redis key
*/
public static final String COOLING = "COOLING_";
/**
* EMS数据 redis key
*/
public static final String EMS = "EMS_";
/**
* 点位匹配数据 redis key
*/
public static final String POINT_MATCH = "POINT_MATCH_";
/**
* 点位枚举匹配数据 redis key
*/
public static final String POINT_ENUM_MATCH = "POINT_ENUM_MATCH_";
/**
* topic对应站点ID redis key
*/
public static final String SITE_ID = "SITE_ID_";
/**
* 存放单个设备同步过来的原始数据-最晚一次数据
*/
public static final String ORIGINAL_MQTT_DATA = "MQTT_";
/**
* 存放单个设备同步过来的告警点位原始数据-最晚一次数据
*/
public static final String ORIGINAL_MQTT_DATA_ALARM = "MQTT_ALARM_";
/** 存放订阅失败告警信息 */
public static final String TOPIC_FAILED_ALRAM_RECORD = "topic_failed_";
/** topic 内没有数据设备维度告警 */
public static final String TOPIC_EMPTY_ALARM_RECORD = "topic_empty_";
/** modbus读取 没有数据设备维度告警 */
public static final String MODBUS_EMPTY_ALARM_RECORD = "modbus_empty_";
/** modbus读取 设备离线告警 */
public static final String MODBUS_OFFLINE_ALARM_RECORD = "modbus_offline_";
/** 设备信息初始化 */
public static final String INIT_DEVICE_INFO = "init_device_info";
/** 设备配置缓存(按站点+设备) */
public static final String DEVICE_SETTING = "DEVICE_SETTING_";
/** 告警匹配信息 */
public static final String ALARM_MATCH_INFO = "alarm_message_info";
/** 现有的告警数据 */
public static final String LATEST_ALARM_RECORD = "LATEST_ALARM_RECORD";
/** 预存电价时间配置 */
public static final String ENERGY_PRICE_TIME = "energy_price_time_";
/** dds昨日累计总收益 */
public static final String DDS_TOTAL_REVENUE = "total_revenue_";
/** fx实时总收益和当日实时收益 */
public static final String FXX_REALTIME_REVENUE = "realtime_revenue_";
/** 每个设备最新数据-设置失效时间-判断是否正常同步数据 */
public static final String SYNC_DATA= "SYNC_DATA_";
/** 每个设备最新数据-设置失效时间-判断是否正常同步数据 */
public static final String SYNC_DATA_ALARM = "SYNC_DATA_ALARM_";
/** 点位配置缓存(按站点+设备) */
public static final String POINT_CONFIG_DEVICE = "POINT_CONFIG_DEVICE_";
/** 点位配置缓存(按站点+pointId */
public static final String POINT_CONFIG_POINT = "POINT_CONFIG_POINT_";
/** 单站监控最新数据(按站点+模块) */
public static final String SITE_MONITOR_LATEST = "SITE_MONITOR_LATEST_";
/** 单站监控点位映射(按站点) */
public static final String SITE_MONITOR_POINT_MATCH = "SITE_MONITOR_POINT_MATCH_";
/** 站点保护约束(按站点) */
public static final String PROTECTION_CONSTRAINT = "PROTECTION_CONSTRAINT_";
}

View File

@ -1,6 +1,8 @@
package com.xzzn.common.core.controller; package com.xzzn.common.core.controller;
import java.beans.PropertyEditorSupport; import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -199,4 +201,76 @@ public class BaseController
{ {
return getLoginUser().getUsername(); return getLoginUser().getUsername();
} }
/**
* 手动处理分页
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TableDataInfo getDataTable2(List<?> list)
{
// 1. 处理原列表为null的情况避免空指针
List<?> targetList = (list == null) ? Collections.emptyList() : list;
List<?> subList;
// 2. 获取分页参数
PageDomain pageDomain = TableSupport.buildPageRequest();
int pageNum = pageDomain.getPageNum();
int pageSize = pageDomain.getPageSize();
// 3. 判断分页参数是否有效pageNum和pageSize均为正数时才分页
if (pageNum > 0 && pageSize > 0) {
// 计算起始索引确保不小于0
int startIndex = Math.max((pageNum - 1) * pageSize, 0);
// 关键修复:若起始索引已超出列表大小,直接返回空列表
if (startIndex >= targetList.size()) {
subList = Collections.emptyList();
} else {
// 计算结束索引(不超过列表大小,且不小于起始索引)
int endIndex = Math.min(startIndex + pageSize, targetList.size());
endIndex = Math.max(endIndex, startIndex); // 防止endIndex < startIndex
// 截取子列表转换为新ArrayList避免视图依赖
subList = new ArrayList<>(targetList.subList(startIndex, endIndex));
}
} else {
// 分页参数无效时,返回全部数据
subList = new ArrayList<>(targetList);
}
// 4. 封装返回结果
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setMsg("查询成功");
rspData.setRows(subList);
rspData.setTotal(targetList.size());
return rspData;
}
/**
* 通用分页工具方法
*/
protected <T> List<T> paginateList(List<T> sourceList) {
if (sourceList == null || sourceList.isEmpty()) {
return new ArrayList<>();
}
// 分页梳理
PageDomain pageDomain = TableSupport.buildPageRequest();
int pageNum = pageDomain.getPageNum();
int pageSize = pageDomain.getPageSize();
int startIndex = 0;
int endIndex = sourceList.size();
if (pageNum > 0 && pageSize > 0) {
// 计算起始索引处理页码小于1的情况
startIndex = Math.max((pageNum - 1) * pageSize, 0);
// 计算结束索引(处理超出列表长度的情况)
endIndex = Math.min(startIndex + pageSize, sourceList.size());
}
// 防止越界
if (startIndex >= sourceList.size()) {
return Collections.emptyList();
}
// 截取分页数据
return sourceList.subList(startIndex, endIndex);
}
} }

View File

@ -92,6 +92,8 @@ public class SysUser extends BaseEntity
/** 角色ID */ /** 角色ID */
private Long roleId; private Long roleId;
private String belongSite;
public SysUser() public SysUser()
{ {
@ -310,6 +312,14 @@ public class SysUser extends BaseEntity
this.roleId = roleId; this.roleId = roleId;
} }
public String getBelongSite() {
return belongSite;
}
public void setBelongSite(String belongSite) {
this.belongSite = belongSite;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -332,7 +342,7 @@ public class SysUser extends BaseEntity
.append("updateBy", getUpdateBy()) .append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime()) .append("updateTime", getUpdateTime())
.append("remark", getRemark()) .append("remark", getRemark())
.append("dept", getDept()) .append("belongSite", getBelongSite())
.toString(); .toString();
} }
} }

View File

@ -0,0 +1,111 @@
package com.xzzn.common.core.modbus;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.ip.IpParameters;
import com.xzzn.common.core.modbus.domain.DeviceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Modbus连接管理器
* 使用长连接模式,维护连接缓存,复用已有连接
*/
@Component
public class Modbus4jConnectionManager {
private static final Logger logger = LoggerFactory.getLogger(Modbus4jConnectionManager.class);
private final ModbusFactory modbusFactory = new ModbusFactory();
private final Map<String, ModbusMaster> connectionCache = new ConcurrentHashMap<>();
/**
* 获取或创建连接(长连接模式)
*/
public ModbusMaster borrowMaster(DeviceConfig config) throws Exception {
String key = buildConnectionKey(config);
ModbusMaster master = connectionCache.get(key);
if (master == null) {
synchronized (this) {
master = connectionCache.get(key);
if (master == null) {
master = createNewConnection(config);
connectionCache.put(key, master);
logger.info("创建新Modbus长连接: {}:{}", config.getHost(), config.getPort());
}
}
}
return master;
}
/**
* 归还连接(长连接模式,不关闭)
*/
public void returnMaster(DeviceConfig config, ModbusMaster master) {
}
/**
* 废弃连接(发生异常时关闭并移除)
*/
public void invalidateMaster(DeviceConfig config, ModbusMaster master) {
String key = buildConnectionKey(config);
connectionCache.remove(key);
destroyMaster(master, config);
logger.warn("废弃并移除Modbus连接: {}:{}", config.getHost(), config.getPort());
}
private ModbusMaster createNewConnection(DeviceConfig config) throws Exception {
IpParameters params = new IpParameters();
params.setHost(config.getHost());
params.setPort(config.getPort());
params.setEncapsulated(false);
ModbusMaster master = modbusFactory.createTcpMaster(params, true);
master.init();
return master;
}
private void destroyMaster(ModbusMaster master, DeviceConfig config) {
if (master != null) {
try {
master.destroy();
logger.debug("已关闭Modbus连接: {}:{}", config.getHost(), config.getPort());
} catch (Exception e) {
logger.warn("关闭Modbus连接异常: {}:{}", config.getHost(), config.getPort(), e);
}
}
}
private String buildConnectionKey(DeviceConfig config) {
return config.getHost() + ":" + config.getPort();
}
/**
* 关闭所有连接
*/
public void closeAllConnections() {
for (Map.Entry<String, ModbusMaster> entry : connectionCache.entrySet()) {
try {
if (entry.getValue() != null) {
entry.getValue().destroy();
}
} catch (Exception e) {
logger.warn("关闭Modbus连接异常: {}", entry.getKey(), e);
}
}
connectionCache.clear();
logger.info("已关闭所有Modbus连接");
}
}

View File

@ -0,0 +1,486 @@
package com.xzzn.common.core.modbus;
import com.alibaba.fastjson2.JSON;
import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.WriteCoilRequest;
import com.serotonin.modbus4j.msg.WriteCoilResponse;
import com.serotonin.modbus4j.msg.WriteCoilsRequest;
import com.serotonin.modbus4j.msg.WriteCoilsResponse;
import com.serotonin.modbus4j.msg.WriteRegisterRequest;
import com.serotonin.modbus4j.msg.WriteRegisterResponse;
import com.serotonin.modbus4j.msg.WriteRegistersRequest;
import com.serotonin.modbus4j.msg.WriteRegistersResponse;
import com.xzzn.common.core.modbus.domain.DeviceConfig;
import com.xzzn.common.core.modbus.domain.TagConfig;
import com.xzzn.common.core.modbus.domain.WriteTagConfig;
import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.ModBusType;
import com.xzzn.common.enums.RegisterType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
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.stereotype.Service;
import static com.xzzn.common.enums.RegisterType.COIL;
import static com.xzzn.common.enums.RegisterType.DISCRETE_INPUT;
@Service
public class ModbusProcessor {
private static final Logger logger = LoggerFactory.getLogger(ModbusProcessor.class);
@Value("${modbus.read-timeout:8000}")
private int readTimeout;
@Value("${modbus.write-timeout:5000}")
private int writeTimeout;
@Value("${modbus.read-retries:1}")
private int readRetries;
@Value("${modbus.write-retries:1}")
private int writeRetries;
@Autowired
private RedisCache redisCache;
@Autowired
private Modbus4jConnectionManager connectionManager;
public boolean writeDataToDevice(DeviceConfig config) {
logger.info("writeDataToDevice: {}", JSON.toJSONString(config));
ModbusMaster master = null;
boolean result = false;
boolean hasError = false;
try {
master = connectionManager.borrowMaster(config);
master.setTimeout(writeTimeout);
master.setRetries(0);
result = writeTagValue(master, config, config.getWriteTags());
} catch (Exception e) {
logger.error("Failed to borrow connection or write to devices '{}'", config.getDeviceName(), e);
hasError = true;
}
finally {
if (master != null) {
if (hasError) {
// 发生异常时废弃连接,下次重新创建
connectionManager.invalidateMaster(config, master);
} else {
// 正常时归还连接
connectionManager.returnMaster(config, master);
}
}
}
return result;
}
public boolean writeDataToDeviceWithRetry(DeviceConfig config) {
logger.info("writeDataToDevice: {}", JSON.toJSONString(config));
ModbusMaster master = null;
boolean result;
try {
master = connectionManager.borrowMaster(config);
master.setTimeout(writeTimeout); // 设置超时时间
master.setRetries(0);
// 使用重试装饰器
ModbusMaster finalMaster = master;
result = RetryableModbusOperation.executeWithRetry(() -> {
return writeTagValue(finalMaster, config, config.getWriteTags());
}, writeRetries); // 最大重试次数由配置控制
} catch (Exception e) {
logger.error("Failed to borrow connection or write to devices '{}'", config.getDeviceName(), e);
result = false;
} finally {
if (master != null) {
connectionManager.returnMaster(config, master);
}
}
return result;
}
public boolean writeTagValue(ModbusMaster master, DeviceConfig config, List<WriteTagConfig> tags) {
tags.forEach(tag -> {
Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
int address = convertAddress(tag.getAddress());
RegisterType registerType = type.get(firstDigit);
logger.info("Register type: {}, address: {}, firstDigit: {}", registerType, tag.getAddress(), firstDigit);
switch (registerType) {
case COIL: {
boolean result = writeCoilRequest(master, config.getSlaveId(), address, Boolean.parseBoolean(String.valueOf(tag.getValue())));
tag.setWrite(result);
break;
}
case HOLDING_REGISTER: {
Number value = Double.parseDouble(String.valueOf(tag.getValue()));
double doubleValue = value.doubleValue();
// 检查是否在16位有符号整数范围内
if (doubleValue < -32768 || doubleValue > 32767) {
logger.warn("Value {} out of range for 16-bit signed register at address {}", doubleValue, address);
}
boolean result = writeRegisterRequest(master, config.getSlaveId(), address, (int) doubleValue);
tag.setWrite(result);
break;
}
default:
logger.error("Unsupported register type: {}", registerType);
break;
}
});
List<WriteTagConfig> collect = tags.stream().filter(tag -> !Objects.equals(tag.isWrite(), true)).collect(Collectors.toList());
if (!collect.isEmpty()) {
return false;
}
return true;
}
public static boolean writeCoilRequest(ModbusMaster master, int slaveId, int address, boolean value) {
try {
WriteCoilRequest request = new WriteCoilRequest(slaveId, address, value);
WriteCoilResponse response = (WriteCoilResponse)master.send(request);
if (response.isException()) {
logger.info("Write coil failed: " + response.getExceptionMessage());
} else {
logger.info("Write coil successful");
return true;
}
} catch (Exception e) {
logger.error("Failed to write coil value '{}' to address '{}'", value, address, e);
}
return false;
}
public static void writeCoilsRequest(ModbusMaster master, int slaveId, int address, boolean[] values) {
try {
WriteCoilsRequest request = new WriteCoilsRequest(slaveId, address, values);
WriteCoilsResponse response = (WriteCoilsResponse)master.send(request);
if (response.isException()) {
logger.info("Write coils failed: " + response.getExceptionMessage());
} else {
logger.info("Write coils successful");
}
} catch (Exception e) {
logger.error("Failed to write coils value '{}' to address '{}'", values, address, e);
}
}
public static boolean writeRegisterRequest(ModbusMaster master, int slaveId, int address, int value) {
try {
WriteRegisterRequest request = new WriteRegisterRequest(slaveId, address, value);
WriteRegisterResponse response = (WriteRegisterResponse)master.send(request);
if (response.isException()) {
logger.info("Write register failed: " + response.getExceptionMessage());
} else {
logger.info("Write register successful");
return true;
}
} catch (Exception e) {
logger.error("Failed to write register value '{}' to address '{}'", value, address, e);
}
return false;
}
public static void writeRegistersRequest(ModbusMaster master, int slaveId, int address, short[] values) {
try {
WriteRegistersRequest request = new WriteRegistersRequest(slaveId, address, values);
WriteRegistersResponse response = (WriteRegistersResponse)master.send(request);
if (response.isException()) {
logger.info("Write registers failed: " + response.getExceptionMessage());
} else {
logger.info("Write registers successful");
}
} catch (Exception e) {
logger.error("Failed to write registers value '{}' to address '{}'", values, address, e);
}
}
public static void setCoilValue(ModbusMaster master, int slaveId, int address, int value) {
// 写入单个线圈(从站ID, 地址, 布尔值)
BaseLocator<Boolean> coilLocator = BaseLocator.coilStatus(slaveId, address);
try {
// 写入布尔值到线圈
master.setValue(coilLocator, value);
logger.info("set coil successful");
} catch (ModbusTransportException | ErrorResponseException e) {
logger.error("Failed to set coil value '{}' to address '{}'", value, address, e);
}
}
public static void setRegisterValue(ModbusMaster master, int slaveId, int address, int value) {
// 写入单个保持寄存器(从站, 地址, 16位整数)
BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, address, DataType.TWO_BYTE_INT_SIGNED);
try {
// 写入整数值到保持寄存器
master.setValue(holdingLocator, value);
logger.info("set register successful");
} catch (ModbusTransportException | ErrorResponseException e) {
logger.error("Failed to set register value '{}' to address '{}'", value, address, e);
}
}
public ModbusMaster borrowMaster(DeviceConfig config) throws Exception {
ModbusMaster master = connectionManager.borrowMaster(config);
// 设置了Modbus通信的超时时间为5000毫秒5秒。当主设备与从设备通信时若在5秒内未收到响应则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。
master.setTimeout(readTimeout);
master.setRetries(readRetries);
return master;
}
public Map<String, Object> readDataFromDevice(DeviceConfig config, ModbusMaster master) {
Map<String, Object> deviceData = new HashMap<>();
boolean hasError = false;
try {
BatchResults<String> results = readTagValues(master, config.getSlaveId(), config.getTags());
for (TagConfig tag : config.getTags()) {
if (Objects.equals(tag.getDataType(), "FOUR_BYTE_FLOAT_DBCA")){
Object value = results.getValue(tag.getKey());
value = convertValueToFloat(value);
deviceData.put(tag.getKey(), value);
}else {
deviceData.put(tag.getKey(), results.getValue(tag.getKey()));
}
}
} catch (Exception e) {
logger.error("Failed read from devices '{}'", config.getDeviceName(), e);
hasError = true;
}
finally {
if (master != null) {
if (hasError) {
// 发生异常时废弃连接,下次重新创建
connectionManager.invalidateMaster(config, master);
} else {
// 正常时归还连接
connectionManager.returnMaster(config, master);
}
}
}
return deviceData;
}
private Object readTagValue(ModbusMaster master, int slaveId, TagConfig tag) throws Exception {
Object value;
Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
Map<String, Integer> DATA_LENGTH = ModBusType.LENGTH;
int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
int address = 0;
int addressLength = tag.getAddress().length();
int exp = (int) Math.pow(10, addressLength-1);
if (firstDigit != 0){
int digit = Integer.parseInt(tag.getAddress());
address = digit % (exp);
}else {
address = Integer.parseInt(tag.getAddress());
}
RegisterType registerType = type.get(firstDigit);
int dataLength = DATA_LENGTH.get(tag.getDataType());
// 增加配置校验和警告
if ((Objects.equals(registerType, COIL) || Objects.equals(registerType, DISCRETE_INPUT))) {
logger.warn("Configuration warning for tag '{}': ModBusType is {} but DataType is {}. DataType should be BOOLEAN. Proceeding with BOOLEAN read.",
tag.getKey(), registerType, tag.getDataType());
}
try {
switch (registerType) {
case COIL: {
BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, address);
value = master.getValue(loc);
logger.info("读取线圈{}成功: {}",tag.getAddress(), value);
break;
}
case DISCRETE_INPUT: {
BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, address);
value = master.getValue(loc);
logger.info("读取输入{}成功: {}",tag.getAddress(), value);
break;
}
case HOLDING_REGISTER: {
if (dataLength == 28){
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, 4);
value = master.getValue(locator);
//将 value 转换为二进制数据
value = convertValueToFloat(value);
}else{
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, dataLength);
value = master.getValue(locator);
}
logger.info("读取保持寄存器{}成功: {}",tag.getAddress(), value);
break;
}
case INPUT_REGISTER: {
BaseLocator<Number> locator = BaseLocator.inputRegister(slaveId, address, dataLength);
value= master.getValue(locator);
logger.info("读取输入寄存器{}成功: {}",tag.getAddress(), value);
break;
}
default:
logger.error("Unsupported register type: {}", registerType);
value = null;
break;
}
}catch (Exception e){
logger.error("Failed to read tag '{}'", tag.getKey(), e);
value = null;
}
return value;
}
private BatchResults<String> readTagValues(ModbusMaster master, int slaveId, List<TagConfig> tags) throws Exception {
try {
BatchResults<String> results = sendBatchRead(master, slaveId, tags);
logBatchResults(tags, results);
return results;
} catch (Exception e){
if (isTimeoutException(e)) {
logger.warn("批量读取超时尝试降级为单点读取slaveId: {}", slaveId);
BatchResults<String> fallback = new BatchResults<>();
for (TagConfig tag : tags) {
Object value = readTagValue(master, slaveId, tag);
fallback.addResult(tag.getKey(), value);
}
logBatchResults(tags, fallback);
return fallback;
}
logger.error("Failed to read master '{}'", slaveId, e);
throw new Exception(e);
}
}
private BatchResults<String> sendBatchRead(ModbusMaster master, int slaveId, List<TagConfig> tags) throws Exception {
BatchRead<String> batch = new BatchRead<>();
tags.forEach(tag -> {
Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
Map<String, Integer> DATA_LENGTH = ModBusType.LENGTH;
int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
int address = 0;
int addressLength = tag.getAddress().length();
int exp = (int) Math.pow(10, addressLength - 1);
if (firstDigit != 0) {
int digit = Integer.parseInt(tag.getAddress());
address = digit % (exp);
} else {
address = Integer.parseInt(tag.getAddress());
}
RegisterType registerType = type.get(firstDigit);
int dataLength = DATA_LENGTH.get(tag.getDataType());
switch (registerType) {
case COIL: {
BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, address);
batch.addLocator(tag.getKey(), loc);
break;
}
case DISCRETE_INPUT: {
BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, address);
batch.addLocator(tag.getKey(), loc);
break;
}
case HOLDING_REGISTER: {
if (dataLength == 28) {
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, 4);
batch.addLocator(tag.getKey(), locator);
} else {
BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, address, dataLength);
batch.addLocator(tag.getKey(), loc);
}
break;
}
case INPUT_REGISTER: {
BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, address, dataLength);
batch.addLocator(tag.getKey(), loc);
break;
}
}
});
return master.send(batch);
}
private void logBatchResults(List<TagConfig> tags, BatchResults<String> results) {
List<String> logInfoList = new ArrayList<>();
for (TagConfig tag : tags) {
StringBuilder logInfo = new StringBuilder();
logInfo.append(tag.getAddress());
if (tag.getBit() != null) {
logInfo.append("(").append(tag.getBit()).append("):");
} else {
logInfo.append(":");
}
logInfo.append(results.getValue(tag.getKey()));
logInfoList.add(logInfo.toString());
}
logger.info("批处理读取寄存器成功: {}", JSON.toJSONString(logInfoList));
}
private boolean isTimeoutException(Throwable e) {
Throwable current = e;
while (current != null) {
if (current instanceof com.serotonin.modbus4j.sero.messaging.TimeoutException) {
return true;
}
current = current.getCause();
}
return false;
}
public static float convertValueToFloat(Object value) {
if (!(value instanceof Number)) {
throw new IllegalArgumentException("Input must be a Number");
}
int intValue = ((Number) value).intValue();
String binaryData = Integer.toBinaryString(intValue);
// 补足32位二进制字符串
if (binaryData.length() < 32) {
binaryData = String.format("%32s", binaryData).replace(' ', '0');
}
// 分割为4个字节并重组为 dcba 格式
byte[] reorderedBytes = new byte[4];
for (int i = 0; i < 4; i++) {
int start = i * 8;
String byteStr = binaryData.substring(start, start + 8);
reorderedBytes[3 - i] = (byte) Integer.parseInt(byteStr, 2);
}
// 使用 ByteBuffer 提高可读性和安全性
return Float.intBitsToFloat(
java.nio.ByteBuffer.wrap(reorderedBytes).getInt()
);
}
/** 转换寄存器地址 */
public static int convertAddress(String address) {
if (StringUtils.isBlank(address)) {
return 0;
}
int firstDigit = Integer.parseInt(address.substring(0, 1));
int addressLength = address.length();
int exp = (int) Math.pow(10, addressLength-1);
if (firstDigit != 0){
int digit = Integer.parseInt(address);
return digit % (exp);
} else {
return Integer.parseInt(address);
}
}
}

View File

@ -0,0 +1,58 @@
package com.xzzn.common.core.modbus;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.ip.IpParameters;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
public class PooledModbusMasterFactory extends BasePooledObjectFactory<ModbusMaster> {
private final String host;
private final int port;
public PooledModbusMasterFactory(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public ModbusMaster create() throws Exception {
IpParameters params = new IpParameters();
params.setHost(this.host);
params.setPort(this.port);
params.setEncapsulated(false);
ModbusMaster master = new ModbusFactory().createTcpMaster(params, true);
master.init();
return master;
}
@Override
public PooledObject<ModbusMaster> wrap(ModbusMaster master) {
return new DefaultPooledObject<>(master);
}
@Override
public void destroyObject(PooledObject<ModbusMaster> p) throws Exception {
if (p.getObject() != null) {
p.getObject().destroy();
}
super.destroyObject(p);
}
@Override
public boolean validateObject(PooledObject<ModbusMaster> p) {
// testConnectivity() 方法在 modbus4j 中不存在。
// 一个真正的验证需要执行一次读操作,但这需要 slaveId而工厂是通用的不知道 slaveId。
// 因此,我们使用一个较弱的验证,只检查 master 对象是否已初始化。
// 这无法检测到被远程设备断开的连接,错误将在实际读取操作中被捕获。
if (p.getObject() == null) {
return false;
}
return p.getObject().isInitialized();
}
}

View File

@ -0,0 +1,37 @@
package com.xzzn.common.core.modbus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Modbus重试
*
*/
public class RetryableModbusOperation {
private static final Logger logger = LoggerFactory.getLogger(RetryableModbusOperation.class);
public interface ModbusOperation<T> {
T execute() throws Exception;
}
public static <T> T executeWithRetry(ModbusOperation<T> operation, int maxRetries) {
int retryCount = 0;
Exception lastException = null;
while (retryCount <= maxRetries) {
try {
return operation.execute();
} catch (Exception e) {
lastException = e;
retryCount++;
if (retryCount <= maxRetries) {
logger.info("Operation failed, retrying... (Attempt {})", retryCount);
}
}
}
logger.error("Max retries ({}) reached. Last error: {}", maxRetries,
lastException != null ? lastException.getMessage() : "Unknown");
throw new RuntimeException("Operation failed after " + maxRetries + " retries", lastException);
}
}

View File

@ -0,0 +1,106 @@
package com.xzzn.common.core.modbus.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "modbus")
public class ModbusProperties {
private PoolConfig pool = new PoolConfig();
private TimeoutConfig timeout = new TimeoutConfig();
public PoolConfig getPool() {
return pool;
}
public void setPool(PoolConfig pool) {
this.pool = pool;
}
public TimeoutConfig getTimeout() {
return timeout;
}
public void setTimeout(TimeoutConfig timeout) {
this.timeout = timeout;
}
public static class PoolConfig {
private int maxTotal = 20;
private int maxIdle = 10;
private int minIdle = 3;
private long maxWait = 5000;
private long timeBetweenEvictionRuns = 30000;
private long minEvictableIdleTime = 60000;
public int getMaxTotal() {
return maxTotal;
}
public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}
public int getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMinIdle() {
return minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public long getMaxWait() {
return maxWait;
}
public void setMaxWait(long maxWait) {
this.maxWait = maxWait;
}
public long getTimeBetweenEvictionRuns() {
return timeBetweenEvictionRuns;
}
public void setTimeBetweenEvictionRuns(long timeBetweenEvictionRuns) {
this.timeBetweenEvictionRuns = timeBetweenEvictionRuns;
}
public long getMinEvictableIdleTime() {
return minEvictableIdleTime;
}
public void setMinEvictableIdleTime(long minEvictableIdleTime) {
this.minEvictableIdleTime = minEvictableIdleTime;
}
}
public static class TimeoutConfig {
private int read = 10000;
private int write = 5000;
public int getRead() {
return read;
}
public void setRead(int read) {
this.read = read;
}
public int getWrite() {
return write;
}
public void setWrite(int write) {
this.write = write;
}
}
}

View File

@ -0,0 +1,91 @@
package com.xzzn.common.core.modbus.domain;
import java.util.List;
public class DeviceConfig {
private String deviceNumber;
private String deviceName;
private String host;
private int port;
private long time;
private int slaveId;
private boolean enabled;
private List<TagConfig> tags;
private List<WriteTagConfig> writeTags;
public String getDeviceNumber() {
return deviceNumber;
}
public void setDeviceNumber(String deviceNumber) {
this.deviceNumber = deviceNumber;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public int getSlaveId() {
return slaveId;
}
public void setSlaveId(int slaveId) {
this.slaveId = slaveId;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public List<TagConfig> getTags() {
return tags;
}
public void setTags(List<TagConfig> tags) {
this.tags = tags;
}
public List<WriteTagConfig> getWriteTags() {
return writeTags;
}
public void setWriteTags(List<WriteTagConfig> writeTags) {
this.writeTags = writeTags;
}
}

View File

@ -0,0 +1,78 @@
package com.xzzn.common.core.modbus.domain;
public class TagConfig {
private String key;
private String des;
private String address;
private String dataType;
private float a;
private float k;
private float b;
private Integer bit;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public float getA() {
return a;
}
public void setA(float a) {
this.a = a;
}
public float getK() {
return k;
}
public void setK(float k) {
this.k = k;
}
public float getB() {
return b;
}
public void setB(float b) {
this.b = b;
}
public Integer getBit() {
return bit;
}
public void setBit(Integer bit) {
this.bit = bit;
}
}

View File

@ -0,0 +1,31 @@
package com.xzzn.common.core.modbus.domain;
public class WriteTagConfig {
private Object value;
private String address;
private boolean isWrite;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public boolean isWrite() {
return isWrite;
}
public void setWrite(boolean write) {
isWrite = write;
}
}

View File

@ -7,10 +7,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -243,6 +241,18 @@ public class RedisCache
return redisTemplate.opsForHash().multiGet(key, hKeys); return redisTemplate.opsForHash().multiGet(key, hKeys);
} }
/**
* 删除Hash中的多条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteAllCacheMapValue(final String key, final Object[] hKey)
{
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/** /**
* 删除Hash中的某条数据 * 删除Hash中的某条数据
* *
@ -255,6 +265,18 @@ public class RedisCache
return redisTemplate.opsForHash().delete(key, hKey) > 0; return redisTemplate.opsForHash().delete(key, hKey) > 0;
} }
/**
* 批量往Hash中存入数据
*
* @param key Redis键
* @param value 值
*/
public <T> void setAllCacheMapValue(final String key, final Map<String, T> value)
{
redisTemplate.opsForHash().putAll(key, value);
}
/** /**
* 获得缓存的基本对象列表 * 获得缓存的基本对象列表
* *
@ -265,4 +287,89 @@ public class RedisCache
{ {
return redisTemplate.keys(pattern); return redisTemplate.keys(pattern);
} }
/**
* 删除list
*
* @param key Redis键
* @return 对象列表
*/
public boolean deleteList(final String key)
{
return redisTemplate.delete(key);
}
/**
* 批量缓存
* @param cacheMap
*/
public <T> void multiSet(final Map<String, String> cacheMap)
{
redisTemplate.opsForValue().multiSet(cacheMap);
}
/**
* 批量设置键值对,并为每个键设置相同的过期时间
* @param keyValueMap 键值对集合
* @param timeout 过期时间
* @param unit 时间单位
*/
public void multiSetWithExpire(Map<String, String> keyValueMap, long timeout, TimeUnit unit) {
if (keyValueMap.isEmpty()) {
return; // 空集合直接返回
}
// 使用Redis管道批量执行命令
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// 循环处理每个键值对
for (Map.Entry<String, String> entry : keyValueMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 1. 设置键值对
operations.opsForValue().set(key, value);
// 2. 设置过期时间
operations.expire(key, timeout, unit);
}
return null; // 管道操作无需返回值
}
});
}
/**
* 删除指定站点的所有历史测试数据缓存(最精准)
* @param testSiteId 测试数据所属的站点ID如"test-site-001"
*/
public void deleteTestCacheBySite(String testSiteId) {
System.out.println("开删除缓存redis" + testSiteId + "<UNK>");
if (testSiteId == null || testSiteId.isEmpty()) {
return;
}
// 构造键前缀测试站点ID开头的所有缓存键无论粒度和时间
String pattern = testSiteId + "_*";
deleteCacheByPattern(pattern);
}
/**
* 根据键前缀批量删除缓存(核心方法)
* @param pattern 键匹配模式支持Redis的通配符*表示任意字符,?表示单个字符)
*/
public void deleteCacheByPattern(String pattern) {
try {
// 1. 查找所有匹配的键注意keys命令在大数据量时可能阻塞Redis建议生产环境用scan
Set<String> keys = redisTemplate.keys(pattern);
if (keys == null || keys.isEmpty()) {
return;
}
// 2. 批量删除匹配的键
long deleteCount = redisTemplate.delete(keys);
System.out.println("删除缓存数据:" + deleteCount + "<UNK>");
} catch (Exception e) {
}
}
} }

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* alarm-告警等级
*
* @author xzzn
*/
public enum AlarmLevelStatus
{
WARNING("A", "提示"), GENERAL("B", "一般"), SERIOUS("C", "严重"), EMERGENCY("D", "紧急");
private final String code;
private final String info;
AlarmLevelStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* alarm-告警状态
*
* @author xzzn
*/
public enum AlarmStatus
{
WAITING("0", "待处理"), DONE("1", "已处理"),PROCESSING("2", "处理中");
private final String code;
private final String info;
AlarmStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,36 @@
package com.xzzn.common.enums;
/**
* ammeter-电表数据类别
*
* @author xzzn
*/
public enum AmmeterCategory
{
CURRENT_FORWARD_ACTIVE("1", "当前正向有功电能"),
CURRENT_FORWARD_REACTIVE("2", "当前正向无功电能"),
CURRENT_REVERSE_ACTIVE("3", "当前反向有功电能"),
CURRENT_REVERSE_REACTIVE("4", "当前反向无功电能"),
A_POWER("3", "A相功率"),
B_POWER("4", "B相功率"),
C_POWER("5", "C相功率");
private final String code;
private final String info;
AmmeterCategory(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* pcs-branch-支路状态
*
* @author xzzn
*/
public enum BranchStatus
{
STANDBY("0", "备用"), NORMAL("1", "正常"), SWITCHING("2", "切换中");
private final String code;
private final String info;
BranchStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* 充电状态&放电状态
*
* @author xzzn
*/
public enum ChargeStatus
{
CHARGING("1", "充电"), STANDBY("2", "待机"), DISCHARGING("3", "放电");
private final String code;
private final String info;
ChargeStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* device-通信状态
*
* @author xzzn
*/
public enum CommunicationStatus
{
OK("0", "正常"), SUSPEND("1", "通信中断") ,EXCEPTION("2", "异常");
private final String code;
private final String info;
CommunicationStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* pcs-控制模式
*
* @author xzzn
*/
public enum ControlModeStatus
{
REMOTE("0", "远程"), LOCAL("1", "本地");
private final String code;
private final String info;
ControlModeStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,42 @@
package com.xzzn.common.enums;
/**
* 电量类型
*/
public enum CostType
{
PEAK("peak", ""),
HIGH("high", ""),
FLAT("flat", ""),
VALLEY("valley", ""),
;
private final String code;
private final String info;
CostType(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
public static CostType getEnumByCode(String code)
{
for (CostType costType : CostType.values()) {
if (costType.code.equals(code)) {
return costType;
}
}
return null;
}
}

View File

@ -0,0 +1,86 @@
package com.xzzn.common.enums;
import org.apache.xmlbeans.impl.common.NameUtil;
import java.util.HashMap;
import java.util.Map;
/**
* device-设备类别
*
* @author xzzn
*/
public enum DeviceCategory
{
PCS("PCS", "PCS设备", null),
BRANCH("BRANCH", "PCS分支设备", PCS),
STACK("STACK", "电池堆", null),
CLUSTER("CLUSTER", "电池簇", STACK),
BATTERY("BATTERY", "单体电池", CLUSTER),
AMMETER("AMMETER", "电表", null),
COOLING("COOLING", "冷却", null),
DH("DH", "动环", null),
XF("XF", "消防", null),
BATTERY_GROUP("BATTERY_GROUP", "电池组", null),
EMS("EMS", "EMS设备", null),;
private final String code;
private final String info;
private final DeviceCategory parentCategory;
DeviceCategory(String code, String info, DeviceCategory parentCategory)
{
this.code = code;
this.info = info;
this.parentCategory = parentCategory;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
public DeviceCategory getParentCategory()
{
return parentCategory;
}
// 缓存info与code的映射优化查询效率
private static final Map<String, String> INFO_CODE_MAP = new HashMap<>();
// 静态块初始化缓存
static {
for (DeviceCategory category : DeviceCategory.values()) {
INFO_CODE_MAP.put(category.info, category.code);
}
}
// 通过info获取code的方法
public static String getCodeByInfo(String info) {
return INFO_CODE_MAP.get(info); // 从缓存中直接获取,效率高
}
// 通过code获取父类code
// 根据字符串编码查找对应的枚举
public static DeviceCategory fromCode(String code) {
for (DeviceCategory category : values()) {
if (category.code.equalsIgnoreCase(code)) { // 忽略大小写,增强兼容性
return category;
}
}
return null;
}
public static String getInfoByCode(String code) {
for (DeviceCategory category : DeviceCategory.values()) {
if (category.code.equals(code)) {
return category.info;
}
}
return null;
}
}

View File

@ -0,0 +1,32 @@
package com.xzzn.common.enums;
/**
* device-通信状态
*
* @author xzzn
*/
public enum DeviceRunningStatus
{
OFFLINE("0", "离线"),
ONLINE("1", "在线");
private final String code;
private final String info;
DeviceRunningStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,20 @@
package com.xzzn.common.enums;
/**
* device-设备类型
*
* @author xzzn
*/
public enum DeviceType
{
/**
* 网络设备
*/
TCP,
/**
* 串口设备
*/
RTU
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* pcs-并网状态
*
* @author xzzn
*/
public enum GridStatus
{
GRID("0", "并网"), NOTGRID("1", "未并网");
private final String code;
private final String info;
GridStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,55 @@
package com.xzzn.common.enums;
import com.serotonin.modbus4j.code.DataType;
import java.util.HashMap;
import java.util.Map;
public class ModBusType {
public static final Map<Integer, RegisterType> REGISTER_TYPE = new HashMap<Integer, RegisterType>();
public static final Map<String, Integer> LENGTH = new HashMap<String, Integer>();
static {
REGISTER_TYPE.put(0, RegisterType.COIL);
REGISTER_TYPE.put(1, RegisterType.DISCRETE_INPUT);
REGISTER_TYPE.put(4, RegisterType.HOLDING_REGISTER);
REGISTER_TYPE.put(3, RegisterType.INPUT_REGISTER);
LENGTH.put("BINARY",DataType.BINARY);
LENGTH.put("TWO_BYTE_INT_UNSIGNED",DataType.TWO_BYTE_INT_UNSIGNED);
LENGTH.put("TWO_BYTE_INT_SIGNED",DataType.TWO_BYTE_INT_SIGNED);
LENGTH.put("TWO_BYTE_INT_UNSIGNED_SWAPPED",DataType.TWO_BYTE_INT_UNSIGNED_SWAPPED);
LENGTH.put("TWO_BYTE_INT_SIGNED_SWAPPED",DataType.TWO_BYTE_INT_SIGNED_SWAPPED);
LENGTH.put("FOUR_BYTE_INT_UNSIGNED",DataType.FOUR_BYTE_INT_UNSIGNED);
LENGTH.put("FOUR_BYTE_INT_SIGNED",DataType.FOUR_BYTE_INT_SIGNED);
LENGTH.put("FOUR_BYTE_INT_UNSIGNED_SWAPPED",DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED);
LENGTH.put("FOUR_BYTE_INT_SIGNED_SWAPPED",DataType.FOUR_BYTE_INT_SIGNED_SWAPPED);
LENGTH.put("FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED",DataType.FOUR_BYTE_INT_UNSIGNED_SWAPPED_SWAPPED);
LENGTH.put("FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED",DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED);
LENGTH.put("FOUR_BYTE_FLOAT",DataType.FOUR_BYTE_FLOAT);
LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED",DataType.FOUR_BYTE_FLOAT_SWAPPED);
LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED_INVERTED",DataType.FOUR_BYTE_FLOAT_SWAPPED_INVERTED);
LENGTH.put("EIGHT_BYTE_INT_UNSIGNED",DataType.EIGHT_BYTE_INT_UNSIGNED);
LENGTH.put("EIGHT_BYTE_INT_SIGNED",DataType.EIGHT_BYTE_INT_SIGNED);
LENGTH.put("EIGHT_BYTE_INT_UNSIGNED_SWAPPED",DataType.EIGHT_BYTE_INT_UNSIGNED_SWAPPED);
LENGTH.put("EIGHT_BYTE_INT_SIGNED_SWAPPED",DataType.EIGHT_BYTE_INT_SIGNED_SWAPPED);
LENGTH.put("EIGHT_BYTE_FLOAT",DataType.EIGHT_BYTE_FLOAT);
LENGTH.put("EIGHT_BYTE_FLOAT_SWAPPED",DataType.EIGHT_BYTE_FLOAT_SWAPPED);
LENGTH.put("TWO_BYTE_BCD",DataType.TWO_BYTE_BCD);
LENGTH.put("FOUR_BYTE_BCD",DataType.FOUR_BYTE_BCD);
LENGTH.put("FOUR_BYTE_BCD_SWAPPED",DataType.FOUR_BYTE_BCD_SWAPPED);
LENGTH.put("CHAR",DataType.CHAR);
LENGTH.put("VARCHAR",DataType.VARCHAR);
LENGTH.put("FOUR_BYTE_FLOAT_DBCA",28);
}
}

View File

@ -0,0 +1,45 @@
package com.xzzn.common.enums;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* 设备点位类型
*/
public enum PointType
{
YES(1, ""),
NO(0, "");
private final Integer code;
private final String info;
private static final Map<Integer, String> CODE_NAME_MAP = new HashMap<>(PointType.values().length);
static {
Arrays.stream(PointType.values()).forEach(record -> {
CODE_NAME_MAP.put(record.code, record.info);
});
}
PointType(Integer code, String info)
{
this.code = code;
this.info = info;
}
public String getInfoByCode(Integer code) {
return CODE_NAME_MAP.get(code);
}
public Integer getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* 告警保护方案状态
*
* @author xzzn
*/
public enum ProtPlanStatus
{
STOP(0L, "未启用"), RUNNING(1L, "已启用");
private final Long code;
private final String info;
ProtPlanStatus(Long code, String info)
{
this.code = code;
this.info = info;
}
public Long getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,8 @@
package com.xzzn.common.enums;
public enum RegisterType {
COIL,
DISCRETE_INPUT,
HOLDING_REGISTER,
INPUT_REGISTER
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* strategy模板-sdc限制
*
* @author xzzn
*/
public enum SdcLimitType
{
OFF("0", ""), ON("1", "");
private final String code;
private final String info;
SdcLimitType(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,67 @@
package com.xzzn.common.enums;
/**
*
*/
public enum SiteDevice
{
/**
* 电池堆
*/
BMSD,
/**
* 电池簇
*/
BMSC,
/**
* PCS设备
*/
PCS,
/**
* 电表-总表
*/
LOAD,
/**
* 电表-光伏电表
*/
METEGF,
/**
* 电表-储能电表
*/
METE,
/**
* 电表-储能电表
*/
METE0,
/**
* 动环
*/
donghuan,
/**
* 动环
*/
DH,
/**
* 消防
*/
XF,
/**
* 中水冷却
*/
ZSLQ,
/**
* EMS
*/
EMS
}

View File

@ -0,0 +1,31 @@
package com.xzzn.common.enums;
/**
* device-设备类型
*
* @author xzzn
*/
public enum SiteEnum
{
DDS("021_DDS_01", "电动所内部"), FX("021_FXX_01", "奉贤西部污水处理厂");
private final String code;
private final String info;
SiteEnum(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,32 @@
package com.xzzn.common.enums;
/**
* SOC限制 (%) 1 = 开0 = 关
*
* @author xzzn
*/
public enum SocLimit
{
OFF(0, ""),
ON(1, ""),
;
private final Integer code;
private final String info;
SocLimit(Integer code, String info)
{
this.code = code;
this.info = info;
}
public Integer getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* strategy-策略状态
*
* @author xzzn
*/
public enum StrategyStatus
{
NOT_ENABLED("0", "未启用"), RUNNING("1", "已运行"),SUSPENDED("2", "已暂停"), DISABLE("3", "禁用"),DELETE("4", "删除");
private final String code;
private final String info;
StrategyStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* pcs-开关状态
*
* @author xzzn
*/
public enum SwitchStatus
{
CLOSED("0", "闭合"), DISCONNECT("1", "断开"), FAULT_DISCONNECT("2", "故障断开");
private final String code;
private final String info;
SwitchStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* ticket - 工单状态
*
* @author xzzn
*/
public enum TicketStatus
{
WAITING("1", "待处理"), PROCESSING("2", "处理中"),DONE("3", "已处理");
private final String code;
private final String info;
TicketStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,39 @@
package com.xzzn.common.enums;
/**
* MQTT主题处理类型未知类型
*/
public enum TopicHandleType {
DEVICE("DEVICE", "设备数据"),
DEVICE_ALARM("DEVICE_ALARM", "设备告警数据"),
STRATEGY("STRATEGY", "策略数据"),
SYSTEM("SYSTEM", "系统数据"),
;
private final String code;
private final String info;
TopicHandleType(String code, String info) {
this.code = code;
this.info = info;
}
public String getName() {
return code;
}
public String getInfo() {
return info;
}
// 根据名称查找枚举
public static TopicHandleType getEnumByCode(String code) {
for (TopicHandleType type : values()) {
if (type.getName().equals(code)) {
return type;
}
}
// 默认返回SYSTEM
return SYSTEM;
}
}

View File

@ -0,0 +1,30 @@
package com.xzzn.common.enums;
/**
* pcs-工作状态
*
* @author xzzn
*/
public enum WorkStatus
{
NORMAL("0", "运行"), STOP("1", "停机"), ABNORMAL("2", "故障");
private final String code;
private final String info;
WorkStatus(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -0,0 +1,33 @@
package com.xzzn.common.enums;
public enum YesOrNo
{
YES(1, "", "Y"),
NO(0, "", "N");
private final Integer code;
private final String info;
private final String value;
YesOrNo(Integer code, String info, String value)
{
this.code = code;
this.info = info;
this.value = value;
}
public Integer getCode()
{
return code;
}
public String getInfo()
{
return info;
}
public String getValue()
{
return value;
}
}

View File

@ -0,0 +1,39 @@
package com.xzzn.common.utils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataUtils {
private static final Logger log = LoggerFactory.getLogger(DataUtils.class);
public static String getJSONFromFile(InputStream inputStream) {
BufferedReader bufferReader = null;
StringBuffer stringBuffer = new StringBuffer();
try {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
bufferReader = new BufferedReader(inputStreamReader);
stringBuffer = new StringBuffer();
String lineData = null;
while ((lineData = bufferReader.readLine()) != null) {
stringBuffer.append(lineData);
}
} catch (Exception e) {
log.warn("getJSONFromFile error", e);
} finally {
if (null != bufferReader) {
try {
bufferReader.close();
inputStream.close();
} catch (Exception e) {
log.warn("getJSONFromFile error", e);
}
}
}
return stringBuffer.toString();
}
}

View File

@ -8,8 +8,14 @@ import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.TimeZone;
import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.logging.LogFactory;
/** /**
* 时间工具类 * 时间工具类
@ -18,6 +24,8 @@ import org.apache.commons.lang3.time.DateFormatUtils;
*/ */
public class DateUtils extends org.apache.commons.lang3.time.DateUtils public class DateUtils extends org.apache.commons.lang3.time.DateUtils
{ {
private static final org.apache.commons.logging.Log log = LogFactory.getLog(DateUtils.class);
public static String YYYY = "yyyy"; public static String YYYY = "yyyy";
public static String YYYY_MM = "yyyy-MM"; public static String YYYY_MM = "yyyy-MM";
@ -28,6 +36,13 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
public static String YYYYMMDD = "yyyyMMdd";
public static String YYYY_MM_DD_HH_MM_00 = "yyyy-MM-dd HH:mm:00";
private static final DateTimeFormatter DAY_INPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter MIN_HOUR_INPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static String[] parsePatterns = { private static String[] parsePatterns = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
@ -144,6 +159,14 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
} }
/**
* 计算相差分钟
*/
public static int differentMinutesByMillisecond(Date date1, Date date2)
{
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 60)));
}
/** /**
* 计算时间差 * 计算时间差
* *
@ -188,4 +211,258 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant()); return Date.from(zdt.toInstant());
} }
public static Long getNowMonthLong() {
Calendar calendar = Calendar.getInstance();
long month = (long) calendar.get(Calendar.MONTH) + 1; // 月份从0开始所以要加1
return month;
}
public static Long getNowDayLong() {
Calendar calendar = Calendar.getInstance();
long date = calendar.get(Calendar.DAY_OF_MONTH); // 月份从0开始所以要加1
return date;
}
// LocalDateTime 转 Date带时区
public static Date convertToDate(LocalDateTime localDateTime) {
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zonedDateTime.toInstant());
}
/**
* 获取昨天的日期格式为yyyy-MM-dd 23:59:59
* @return 昨天的日期字符串
*/
public static String getYesterdayDate() {
// 获取今天的日期
LocalDate today = LocalDate.now();
// 减去一天得到昨天
LocalDate yesterday = today.minusDays(1);
// 将日期转换为LocalDateTime并设置时间为23:59:59
LocalDateTime yesterdayEndTime = yesterday.atTime(23, 59, 59);
// 定义日期格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);
// 格式化并返回
return yesterdayEndTime.format(formatter);
}
/**
* 校验传入日期是否今天
* @param dataDate
* @return
*/
public static boolean checkIsToday(String dataDate) {
boolean flag = false;
Calendar calendar = Calendar.getInstance();
int todayMonth = calendar.get(Calendar.MONTH) + 1;
int todayDay = calendar.get(Calendar.DAY_OF_MONTH);
if (StringUtils.isNotEmpty(dataDate)){
String[] pcsDateArray = dataDate.split("-");
if (todayMonth == Integer.parseInt(pcsDateArray[1]) &&
todayDay == Integer.parseInt(pcsDateArray[2])) {
flag = true;
}
}
return flag;
}
/**
* 将时间转换为月份YYYY-MM
*/
public static String formatMonth(LocalDateTime time) {
return time.format(DateTimeFormatter.ofPattern("yyyy-MM"));
}
/**
* 增加 LocalDateTime ==> String
*/
public static String convertToString(LocalDateTime time) {
return time.format(DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS));
}
/**
* 将输入时间调整到下一分钟的整点秒数强制为00
* 例如2026-09-03 18:34:49 -> 2026-09-03 18:35:00
* @param timeStr 输入时间字符串支持yyyy-MM-dd HH:mm:ss格式
* @return 调整后的时间字符串yyyy-MM-dd HH:mm:00
* @throws ParseException 时间格式错误时抛出
*/
public static String adjustToNextMinute(String timeStr) throws ParseException {
// 1. 解析原始时间(使用包含秒数的格式)
SimpleDateFormat fullFormat = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS);
Date originalDate = fullFormat.parse(timeStr);
// 2. 使用Calendar调整时间
Calendar cal = Calendar.getInstance();
cal.setTime(originalDate);
// 3. 如果当前秒数大于0自动进1分钟否则保持当前分钟
if (cal.get(Calendar.SECOND) > 0) {
cal.add(Calendar.MINUTE, 1); // 分钟+1
}
// 4. 强制设置秒数和毫秒为0
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
// 5. 格式化为目标字符串
return new SimpleDateFormat(YYYY_MM_DD_HH_MM_00).format(cal.getTime());
}
/**
* 将"yyyy-MM-dd"字符串转换为Date类型的"yyyy-MM-dd 23:59:59"
*/
public static Date adjustToEndOfDay(String dateStr) {
// 1. 解析字符串为LocalDate仅日期
LocalDate localDate = LocalDate.parse(dateStr, DAY_INPUT_FORMATTER);
// 2. 补充时间为23:59:59转换为LocalDateTime
LocalDateTime localDateTime = localDate.atTime(23, 59, 59);
// 3. 转换为Date类型兼容旧API
return Date.from(
localDateTime.atZone(TimeZone.getDefault().toZoneId()) // 适配系统时区
.toInstant()
);
}
/**
* 将 "yyyy-MM-dd HH:mm:ss" 字符串转换为Date类型的"yyyy-MM-dd HH:00:00"
*/
public static Date adjustToStartOfHour(String inputTime) {
// 1. 解析输入字符串为LocalDateTime包含日期和小时、分钟
LocalDateTime inputLdt = LocalDateTime.parse(inputTime, MIN_HOUR_INPUT_FORMATTER);
// 2. 调整分钟为00秒为00保留日期和小时不变
LocalDateTime adjustedLdt = inputLdt
.withMinute(00) // 强制设为00分
.withSecond(00); // 强制设为00秒
// 3. 转换为Date类型适配旧API
return Date.from(
adjustedLdt.atZone(TimeZone.getDefault().toZoneId()) // 适配系统时区
.toInstant()
);
}
/**
* 将 "yyyy-MM-dd HH:mm:ss" 字符串转换为Date类型的"yyyy-MM-dd HH:mm:00"
*/
public static Date adjustToStartOfMinutes(String inputTime) {
// 1. 解析输入字符串为LocalDateTime包含日期和小时、分钟
LocalDateTime inputLdt = LocalDateTime.parse(inputTime, MIN_HOUR_INPUT_FORMATTER);
// 2. 调整分钟为00秒为00保留日期和小时不变
LocalDateTime adjustedLdt = inputLdt
.withSecond(00); // 强制设为00秒
// 3. 转换为Date类型适配旧API
return Date.from(
adjustedLdt.atZone(TimeZone.getDefault().toZoneId()) // 适配系统时区
.toInstant()
);
}
/**
* 将时间转换为月份YYYY-MM-DD HH:mm:00
*/
public static Date timeConvertToDate(LocalDateTime time) {
String dateStr = convertToString(time);
return dateTime(YYYY_MM_DD_HH_MM_SS,dateStr);
}
/**
* 增加 Date ==> LocalDateTime
*/
public static LocalDateTime toLocalDateTime(Date date)
{
if (date == null) {
return null;
}
// 1. Date → LocalDateTime保留原始时间
LocalDateTime dateTime = date.toInstant()
.atZone(TimeZone.getDefault().toZoneId()) // 关联时区
.toLocalDateTime();
return dateTime;
}
// 时间戳转时间
public static Date convertUpdateTime(Long updateTime) {
if (updateTime == null) {
return null;
}
// 兼容10位秒级和13位毫秒级
int length = String.valueOf(updateTime).length();
if (length == 10) { // 10位秒级 -> 转换为毫秒级
updateTime *= 1000;
} else if (length != 13) { // 既不是10位也不是13位视为非法
System.err.println("时间戳格式错误必须是10位(秒)或13位(毫秒)");
return null;
}
// 3. 转换为Date
return new Date(updateTime);
}
/**
* 获取昨天的日期格式为yyyy-MM-dd
* @return 昨天的日期字符串
*/
public static String getYesterdayDayString() {
// 获取今天的日期
LocalDate today = LocalDate.now();
// 减去一天得到昨天
LocalDate yesterday = today.minusDays(1);
// 定义日期格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD);
// 格式化并返回
return yesterday.format(formatter);
}
/**
* 获取日期的开始时间
* @param dateString 格式为yyyy-MM-dd的日期字符串
* @return 日期的开始时间字符串
*/
public static String getDayBeginString(String dateString) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
try {
// 解析日期字符串
LocalDate date = LocalDate.parse(dateString, formatter);
// 创建时间段的开始时间 00:00:00
LocalDateTime startTime = date.atTime(LocalTime.MIN);
return convertToString(startTime);
} catch (DateTimeParseException e) {
log.info("Error parsing date: " + e.getMessage());
}
return dateString + " 00:00:00";
}
/**
* 获取日期的结束时间
* @param dateString 格式为yyyy-MM-dd的日期字符串
* @return 日期的结束时间字符串
*/
public static String getDayEndString(String dateString) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
try {
// 解析日期字符串
LocalDate date = LocalDate.parse(dateString, formatter);
// 创建时间段的结束时间 23:59:59
LocalDateTime endTime = date.atTime(LocalTime.MAX);
return convertToString(endTime);
} catch (DateTimeParseException e) {
log.info("Error parsing date: " + e.getMessage());
}
return dateString + " 23:59:59";
}
} }

View File

@ -0,0 +1,44 @@
package com.xzzn.common.utils;
import java.util.Map;
public class MapUtils {
/**
* 从Map中获取值并转为Integer适配tinyint字段
* @param map 数据源Map
* @param key 字段名
* @return 转换后的Integer默认返回0可根据业务调整默认值
*/
public static Integer getInteger(Map<String, Object> map, String key) {
// 1. 处理Map为null或key不存在的情况
if (map == null || !map.containsKey(key)) {
return 0;
}
// 2. 获取原始值
Object value = map.get(key);
if (value == null) {
return 0;
}
// 3. 转换为Integer处理常见类型
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Long) {
// 若Map中存的是Long如JSON解析时数字默认转为Long
return ((Long) value).intValue();
} else if (value instanceof String) {
// 若值是字符串类型(如"1"
try {
return Integer.parseInt((String) value);
} catch (NumberFormatException e) {
// 字符串无法转数字,返回默认值
return 0;
}
} else if (value instanceof Boolean) {
// 特殊情况布尔值转整数true→1false→0
return (Boolean) value ? 1 : 0;
} else {
// 其他不支持的类型,返回默认值
return 0;
}
}
}

View File

@ -1,14 +1,17 @@
package com.xzzn.common.utils; package com.xzzn.common.utils;
import com.xzzn.common.constant.Constants;
import com.xzzn.common.core.text.StrFormatter;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import com.xzzn.common.constant.Constants;
import com.xzzn.common.core.text.StrFormatter;
/** /**
* 字符串工具类 * 字符串工具类
@ -719,4 +722,49 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
} }
return sb.toString(); return sb.toString();
} }
public static BigDecimal getBigDecimal(Object s){
BigDecimal result;
try {
result = new BigDecimal(s.toString());
} catch (Exception e) {
return BigDecimal.ZERO;
}
return result;
}
public static Long getLong(Object s){
Long result;
try {
result = Long.parseLong(s.toString());
} catch (Exception e) {
return Long.parseLong("0");
}
return result;
}
public static String getString(Object s){
if (s == null) return null;
String result;
try {
result = String.valueOf(s);
} catch (Exception e) {
return "0";
}
return result;
}
// 补全三位数字(返回字符串)
public static String fillThreeDigits(String value) {
if (value == null || value.trim().isEmpty()) {
return "000";
}
try {
int num = Integer.parseInt(value.trim());
return String.format("%03d", num);
} catch (NumberFormatException e) {
return value;
}
}
} }

View File

@ -46,7 +46,20 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-annotations</artifactId>
</dependency>
<!-- 获取系统信息 --> <!-- 获取系统信息 -->
<dependency> <dependency>
<groupId>com.github.oshi</groupId> <groupId>com.github.oshi</groupId>
@ -60,5 +73,4 @@
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,97 @@
package com.xzzn.framework.aspectj;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsDeviceChangeLog;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.UUID;
/**
* 设备运行状态变更日志同步
* 本地 - 云端
*/
@Aspect
@Component
public class DeviceChangeLogAspect {
private static final Log logger = LogFactory.getLog(DeviceChangeLogAspect.class);
@Autowired
private MqttPublisher mqttPublisher;
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String MQTT_TOPIC = "DEVICE_CHANGE_LOG_UP";
private static final String TABLE_NAME = "ems_device_change_log";
@Autowired
private MqttSyncLogMapper mqttSyncLogMapper;
@Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
// 定义切点拦截策略相关表的Mapper方法
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsDeviceChangeLogMapper.insertEmsDeviceChangeLog(..)) && args(insertEntity)) ")
public void insertPointCut(EmsDeviceChangeLog insertEntity) {
logger.info("【新增设备运行状态变更日志】DeviceChangeLogAspect 实例化");
}
// 方法执行成功后发布同步消息
@AfterReturning(pointcut = "insertPointCut(insertEntity)", returning = "result")
public void afterInsert(JoinPoint joinPoint, EmsDeviceChangeLog insertEntity, Integer result) {
logger.info("【新增设备运行状态变更日志下发数据切面进入成功】");
if (result == 0 || insertEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String operateType = "INSERT";
String siteId = insertEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType,siteId);
try {
// 数据转换
String content = objectMapper.writeValueAsString(insertEntity);
message.setContent(content);
// mqtt同步到云端
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
// 构建同步信息
private MqttSyncLog createMessageObject(String operateType, String siteId) {
MqttSyncLog message = new MqttSyncLog();
message.setSyncId(UUID.randomUUID().toString());
message.setOperateType(operateType);
message.setTableName(TABLE_NAME);
message.setCreateTime(new Date());
message.setTopic(MQTT_TOPIC);
message.setStatus("SUCCESS");
message.setSyncObject(siteId);
message.setTarget("CLOUD");
return message;
}
}

View File

@ -0,0 +1,104 @@
package com.xzzn.framework.aspectj;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsAlarmRecords;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.UUID;
/**
* 本地保护告警信息同步云端
* 本地 - 云端
* @author xzzn
*/
@Aspect
@Component
public class FaultPlanAlarmAspect
{
private static final Log logger = LogFactory.getLog(FaultPlanAlarmAspect.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private MqttSyncLogMapper mqttSyncLogMapper;
@Autowired
private MqttPublisher mqttPublisher;
@Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
private static final String MQTT_TOPIC = "FAULT_ALARM_RECORD_UP";
private static final String TABLE_NAME = "ems_alarm_records";
// 切入点:拦截所有添加了@SyncAfterInsert注解的方法
@Pointcut("@annotation(com.xzzn.common.annotation.SyncAfterInsert)")
public void syncInsertPointcut() {}
// 方法执行成功后同步数据
@AfterReturning(pointcut = "syncInsertPointcut()", returning = "result")
public void afterInsert(JoinPoint joinPoint, Integer result) {
logger.info("【新增保护策略告警数据切面进入成功】");
if (result == 0) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 获取参数中的EmsAlarmRecords对象
Object[] args = joinPoint.getArgs();
EmsAlarmRecords alarmRecords = null;
for (Object arg : args) {
if (arg instanceof EmsAlarmRecords) {
alarmRecords = (EmsAlarmRecords) arg;
break;
}
}
if (alarmRecords == null) {
return;
}
// 构建日志同步消息
MqttSyncLog message = createMessageObject("INSERT", alarmRecords.getSiteId());
try {
// 同步内容
String content = objectMapper.writeValueAsString(alarmRecords);
message.setContent(content);
// 发布到MQTT主题-同步到云端
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
// 构建同步信息
private MqttSyncLog createMessageObject(String operateType, String siteId) {
MqttSyncLog message = new MqttSyncLog();
message.setSyncId(UUID.randomUUID().toString());
message.setOperateType(operateType);
message.setTableName(TABLE_NAME);
message.setCreateTime(new Date());
message.setTopic(MQTT_TOPIC);
message.setStatus("SUCCESS");
message.setSyncObject(siteId);
message.setTarget("CLOUD");
return message;
}
}

View File

@ -0,0 +1,98 @@
package com.xzzn.framework.aspectj;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsFaultIssueLog;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 保护告警方案下发日志同步
* 本地 - 云端
*/
@Aspect
@Component
public class FaultPlanIssueAspect {
private static final Log logger = LogFactory.getLog(FaultPlanIssueAspect.class);
@Autowired
private MqttPublisher mqttPublisher;
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String MQTT_TOPIC = "FAULT_PLAN_ISSUE_UP";
private static final String TABLE_NAME = "ems_fault_issue_log";
@Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
@Autowired
private MqttSyncLogMapper mqttSyncLogMapper;
// 定义切点拦截策略相关表的Mapper方法
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsFaultIssueLogMapper.insertEmsFaultIssueLog(..)) && args(insertEntity)) ")
public void insertPointCut(EmsFaultIssueLog insertEntity) {
logger.info("【新增保护告警策略下发数据】FaultPlanIssueAspect 实例化");
}
// 方法执行成功后发布同步消息
@AfterReturning(pointcut = "insertPointCut(insertEntity)", returning = "result")
public void afterInsert(JoinPoint joinPoint, EmsFaultIssueLog insertEntity, Integer result) {
logger.info("【新增保护告警策略下发数据切面进入成功】");
if (result == 0 || insertEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String operateType = "INSERT";
String siteId = insertEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType,siteId);
try {
// 数据转换
String content = objectMapper.writeValueAsString(insertEntity);
message.setContent(content);
// mqtt同步到云端
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
// 构建同步信息
private MqttSyncLog createMessageObject(String operateType, String siteId) {
MqttSyncLog message = new MqttSyncLog();
message.setSyncId(UUID.randomUUID().toString());
message.setOperateType(operateType);
message.setTableName(TABLE_NAME);
message.setCreateTime(new Date());
message.setTopic(MQTT_TOPIC);
message.setStatus("SUCCESS");
message.setSyncObject(siteId);
message.setTarget("CLOUD");
return message;
}
}

View File

@ -0,0 +1,277 @@
package com.xzzn.framework.aspectj;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsFaultProtectionPlan;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 设备保护告警方案同步
* 云端 - 本地
*/
@Aspect
@Component
public class FaultProtPlanAspect {
private static final Log logger = LogFactory.getLog(FaultProtPlanAspect.class);
@Autowired
private MqttPublisher mqttPublisher;
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String MQTT_TOPIC = "FAULT_PROTECTION_PLAN_UP";
private static final String TABLE_NAME = "ems_fault_protection_plan";
@Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
@Autowired
private MqttSyncLogMapper mqttSyncLogMapper;
@Autowired
private EmsFaultProtectionPlanMapper emsFaultProtectionPlanMapper;
// 用ThreadLocal暂存删除前的对象
private ThreadLocal<EmsFaultProtectionPlan> beforeDeleteThreadLocal = new ThreadLocal<>();
@Before("execution(* com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper.deleteEmsFaultProtectionPlanByIds(..)) && args(ids)")
public void beforeDelete(JoinPoint joinPoint, Long[] ids) {
// 获取删除的id
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
Long[] id = (Long[]) args[0];
// 查询删除前的数据
EmsFaultProtectionPlan faultInfo = emsFaultProtectionPlanMapper.selectEmsFaultProtectionPlanById(id[0]);
beforeDeleteThreadLocal.set(faultInfo); // 暂存
}
// 定义切点拦截策略相关表的Mapper方法
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper.insertEmsFaultProtectionPlan(..)) && args(insertEntity)) ")
public void insertPointCut(EmsFaultProtectionPlan insertEntity) {
logger.info("【新增设备保护告警】FaultProtPlanAspect 实例化");
}
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(..)) && args(updateEntity)) ")
public void updatePointCut(EmsFaultProtectionPlan updateEntity) {
logger.info("【更新设备保护告警】FaultProtPlanAspect 实例化");
}
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper.deleteEmsFaultProtectionPlanByIds(..)) && args(ids)) ")
public void deletePointCut(Long[] ids) {
logger.info("【删除设备保护告警】FaultProtPlanAspect 实例化");
}
// 方法执行成功后发布同步消息
@AfterReturning(pointcut = "insertPointCut(insertEntity)", returning = "result")
public void afterInsert(JoinPoint joinPoint, EmsFaultProtectionPlan insertEntity, Integer result) {
logger.info("【新增设备保护告警切面进入成功】");
if (result == 0 || insertEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
String siteId = insertEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType,siteId);
try {
// 数据转换
String content = convertEntityToJson(insertEntity);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
@AfterReturning(pointcut = "updatePointCut(updateEntity)", returning = "result")
public void afterUpdate(JoinPoint joinPoint, EmsFaultProtectionPlan updateEntity, Integer result) {
logger.info("【更新设备保护告警切面进入成功】");
if (result == 0 || updateEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
String siteId = updateEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType,siteId);
try {
// 数据转换
String content = convertEntityToJson(updateEntity);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
@AfterReturning(pointcut = "deletePointCut(id)", returning = "result")
public void afterDelete(JoinPoint joinPoint, Long[] id, Integer result) {
logger.info("【删除设备保护告警切面进入成功】");
if (result == 0 || id == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
// 从ThreadLocal中获取删除前的对象
EmsFaultProtectionPlan faultInfo = beforeDeleteThreadLocal.get();
if (faultInfo == null) {
return;
}
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType,faultInfo.getSiteId());
try {
// 数据转换
String content = convertEntityToJson(faultInfo);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
// 构建同步信息
private MqttSyncLog createMessageObject(String operateType, String siteId) {
MqttSyncLog message = new MqttSyncLog();
message.setSyncId(UUID.randomUUID().toString());
message.setOperateType(operateType);
message.setTableName(TABLE_NAME);
message.setCreateTime(new Date());
message.setTopic(MQTT_TOPIC);
message.setStatus("SUCCESS");
message.setSyncObject("CLOUD");
message.setTarget(siteId);
return message;
}
// 从方法名判断操作类型示例insert→INSERTupdate→UPDATEdelete→DELETE
private String getOperateType(String methodName) {
if (methodName.startsWith("insert")) return "INSERT";
if (methodName.startsWith("stop")) return "STOP";
if (methodName.startsWith("update") || methodName.startsWith("stop")) return "UPDATE";
if (methodName.startsWith("delete")) return "DELETE";
return "UNKNOWN";
}
// 从方法参数提取数据示例若参数是实体类转成Map
private Map<String, Object> extractDataFromParams(Object[] args) {
// 实际需反射获取实体类的字段和值如id、name等
Map<String, Object> data = new HashMap<>();
if (args == null || args.length == 0) {
return data;
}
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg == null) {
continue; // 跳过null参数
}
// 处理基本类型/包装类/字符串直接作为值存入key为"param0"、"param1"等)
if (isBasicType(arg.getClass())) {
String key = "param" + i; // 基本类型参数用"param0"、"param1"作为key
data.put(key, arg);
} else {
Map<String, Object> beanMap = beanToMap(arg);
data.putAll(beanMap); // 合并实体类的字段到结果Map
}
}
return data;
}
/**
* 判断是否为基本类型或包装类或字符串
*/
private boolean isBasicType(Class<?> clazz) {
return clazz.isPrimitive() // 基本类型int、long、boolean等
|| clazz == String.class // 字符串
|| Number.class.isAssignableFrom(clazz) // 数字包装类Integer、Long等
|| clazz == Boolean.class; // 布尔包装类
}
/**
* 将实体类转换为Map字段名为key字段值为value
*/
private Map<String, Object> beanToMap(Object bean) {
Map<String, Object> map = new HashMap<>();
if (bean == null) {
return map;
}
// 方式1使用BeanMap简洁高效
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(key.toString(), beanMap.get(key));
}
return map;
}
// 在方法中转换
public String convertEntityToJson(EmsFaultProtectionPlan insertEntity) throws Exception {
if (insertEntity == null) {
return null; // 空对象返回空JSON
}
// 将实体类转换为JSON字符串
return objectMapper.writeValueAsString(insertEntity);
}
}

View File

@ -25,7 +25,7 @@ import com.xzzn.common.utils.ip.IpUtils;
* @author xzzn * @author xzzn
*/ */
@Aspect @Aspect
@Component @Component("customRateLimiterAspect")
public class RateLimiterAspect public class RateLimiterAspect
{ {
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);

View File

@ -0,0 +1,272 @@
package com.xzzn.framework.aspectj;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsStrategyRunning;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.domain.vo.StrategyRunningVo;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component;
/**
* 策略运行切面同步
* 云端 - 本地
*/
@Aspect
@Component
public class StrategyRunningSyncAspect {
private static final Logger logger = LoggerFactory.getLogger(StrategyRunningSyncAspect.class);
@Autowired
private MqttPublisher mqttPublisher;
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String MQTT_TOPIC = "EMS_STRATEGY_UP";
private static final String TABLE_NAME = "ems_strategy_running";
@Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
@Autowired
private MqttSyncLogMapper mqttSyncLogMapper;
@Autowired
private EmsStrategyRunningMapper emsStrategyRunningMapper;
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyRunningMapper.stopEmsStrategyRunning(..)) && args(id)) ")
public void stopPointCut(Long id) {
logger.info("【停止策略切面】StrategyAspect 被实例化");
}
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyRunningMapper.insertEmsStrategyRunning(..)) && args(insertEntity)) ")
public void insertPointCut(EmsStrategyRunning insertEntity) {
logger.info("【新增策略切面】StrategyAspect 被实例化");
}
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyRunningMapper.updateEmsStrategyRunning(..)) && args(updateEntity)) ")
public void updatePointCut(EmsStrategyRunning updateEntity) {
logger.info("【更新策略切面】StrategyAspect 被实例化");
}
// 方法执行成功后发布同步消息
@AfterReturning(pointcut = "updatePointCut(updateEntity)", returning = "result")
public void afterUpdate(JoinPoint joinPoint, EmsStrategyRunning updateEntity, Integer result) {
logger.info("【更新策略切面进入成功】");
if (result == 0 || updateEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
String siteId = updateEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType,siteId);
try {
// 数据转换
List<StrategyRunningVo> runningVos = emsStrategyRunningMapper.getRunningList(siteId);
if (runningVos == null || runningVos.size() == 0) {
return;
}
StrategyRunningVo runningVo = runningVos.get(0);
String content = objectMapper.writeValueAsString(runningVo);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
@AfterReturning(pointcut = "insertPointCut(insertEntity)", returning = "result")
public void afterInsert(JoinPoint joinPoint, EmsStrategyRunning insertEntity, Integer result) {
logger.info("【新增策略切面进入成功】");
if (result == 0 || insertEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
String siteId = insertEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType, siteId);
try {
// 数据转换
List<StrategyRunningVo> runningVos = emsStrategyRunningMapper.getRunningList(siteId);
if (runningVos == null || runningVos.size() == 0) {
return;
}
StrategyRunningVo runningVo = runningVos.get(0);
String content = objectMapper.writeValueAsString(runningVo);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
@AfterReturning(pointcut = "stopPointCut(id)", returning = "result")
public void afterStop(JoinPoint joinPoint, Long id, Integer result) {
logger.info("【停止策略切面进入成功】");
if (result == 0 || id == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
EmsStrategyRunning emsStrategyRunning = emsStrategyRunningMapper.selectEmsStrategyRunningById(id);
String siteId = emsStrategyRunning==null ? null : emsStrategyRunning.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType, siteId);
try {
// 数据转换
Map<String, Object> idMap = new HashMap<>();
idMap.put("id", id); // 手动将参数值映射到"id"字段
String content = JSON.toJSONString(idMap);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
// 构建同步信息
private MqttSyncLog createMessageObject(String operateType, String siteId) {
MqttSyncLog message = new MqttSyncLog();
message.setSyncId(UUID.randomUUID().toString());
message.setOperateType(operateType);
message.setTableName(TABLE_NAME);
message.setCreateTime(new Date());
message.setTopic(MQTT_TOPIC);
message.setStatus("SUCCESS");
message.setSyncObject("CLOUD");
message.setTarget(siteId);
return message;
}
// 从方法名判断操作类型示例insert→INSERTupdate→UPDATEdelete→DELETE
private String getOperateType(String methodName) {
if (methodName.startsWith("insert")) return "INSERT";
if (methodName.startsWith("stop")) return "STOP";
if (methodName.startsWith("update") || methodName.startsWith("stop")) return "UPDATE";
if (methodName.startsWith("delete")) return "DELETE";
return "UNKNOWN";
}
// 从方法参数提取数据示例若参数是实体类转成Map
private Map<String, Object> extractDataFromParams(Object[] args) {
// 实际需反射获取实体类的字段和值如id、name等
Map<String, Object> data = new HashMap<>();
if (args == null || args.length == 0) {
return data;
}
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg == null) {
continue; // 跳过null参数
}
// 处理基本类型/包装类/字符串直接作为值存入key为"param0"、"param1"等)
if (isBasicType(arg.getClass())) {
String key = "param" + i; // 基本类型参数用"param0"、"param1"作为key
data.put(key, arg);
} else {
Map<String, Object> beanMap = beanToMap(arg);
data.putAll(beanMap); // 合并实体类的字段到结果Map
}
}
return data;
}
/**
* 判断是否为基本类型或包装类或字符串
*/
private boolean isBasicType(Class<?> clazz) {
return clazz.isPrimitive() // 基本类型int、long、boolean等
|| clazz == String.class // 字符串
|| Number.class.isAssignableFrom(clazz) // 数字包装类Integer、Long等
|| clazz == Boolean.class; // 布尔包装类
}
/**
* 将实体类转换为Map字段名为key字段值为value
*/
private Map<String, Object> beanToMap(Object bean) {
Map<String, Object> map = new HashMap<>();
if (bean == null) {
return map;
}
// 方式1使用BeanMap简洁高效
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(key.toString(), beanMap.get(key));
}
return map;
}
// 在方法中转换
public String convertEntityToJson(EmsStrategyRunning insertEntity) throws Exception {
if (insertEntity == null) {
return null; // 空对象返回空JSON
}
// 将实体类转换为JSON字符串
return objectMapper.writeValueAsString(insertEntity);
}
}

View File

@ -0,0 +1,261 @@
package com.xzzn.framework.aspectj;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.common.utils.bean.BeanUtils;
import com.xzzn.ems.domain.EmsStrategy;
import com.xzzn.ems.domain.EmsStrategyRunning;
import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.domain.vo.SyncStrategyTempVo;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component;
/**
* 策略模板数据同步
* 云端 - 本地
*/
@Aspect
@Component
public class StrategyTempSyncAspect {
private static final Logger logger = LoggerFactory.getLogger(StrategyTempSyncAspect.class);
@Autowired
private MqttPublisher mqttPublisher;
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String MQTT_TOPIC = "EMS_STRATEGY_UP";
private static final String TABLE_NAME = "ems_strategy_temp";
@Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
@Autowired
private MqttSyncLogMapper mqttSyncLogMapper;
@Autowired
private EmsStrategyMapper emsStrategyMapper;
@Autowired
private EmsStrategyTempMapper emsStrategyTempMapper;
// 用ThreadLocal暂存删除前的对象
private ThreadLocal<EmsStrategyTemp> beforeDeleteThreadLocal = new ThreadLocal<>();
@Before("execution(* com.xzzn.ems.mapper.EmsStrategyTempMapper.deleteTempByTempId(..)) && args(templateId)")
public void beforeDelete(JoinPoint joinPoint, String templateId) {
// 查询删除前的数据-仅存一获取siteId
List<EmsStrategyTemp> tempList = emsStrategyTempMapper.selectStrategyTempByTempId(templateId);
if (tempList != null && tempList.size() > 0) {
beforeDeleteThreadLocal.set(tempList.get(0)); // 暂存
}
}
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyTempMapper.deleteTempByTempId(..)) && args(templateId)) ")
public void deletePointCut(String templateId) {
logger.info("【删除策略模版切面】StrategyTempSyncAspect 被实例化");
}
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyTempMapper.insertEmsStrategyTemp(..)) && args(insertEntity)) ")
public void insertPointCut(EmsStrategyTemp insertEntity) {
logger.info("【新增策略模版切面】StrategyTempSyncAspect 被实例化");
}
// 方法执行成功后发布同步消息
@AfterReturning(pointcut = "insertPointCut(insertEntity)", returning = "result")
public void afterInsert(JoinPoint joinPoint, EmsStrategyTemp insertEntity, Integer result) {
logger.info("【新增策略切面进入成功】");
if (result == 0 || insertEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
String siteId = insertEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType, siteId);
try {
// 校验策略id是否存在
Long strategyId = insertEntity.getStrategyId();
if (strategyId == null) {
return;
}
// 数据转换
SyncStrategyTempVo tempVo = convertEntity(insertEntity);
String content = JSON.toJSONString(tempVo);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
private SyncStrategyTempVo convertEntity(EmsStrategyTemp insertEntity) {
SyncStrategyTempVo tempVo = new SyncStrategyTempVo();
BeanUtils.copyProperties(insertEntity, tempVo);
EmsStrategy strategy = emsStrategyMapper.selectEmsStrategyById(insertEntity.getStrategyId());
if (strategy != null) {
tempVo.setStrategyName(strategy.getStrategyName());
tempVo.setStrategyType(strategy.getStrategyType());
}
return tempVo;
}
@AfterReturning(pointcut = "deletePointCut(templateId)", returning = "result")
public void afterDelete(JoinPoint joinPoint, String templateId, Integer result) {
logger.info("【删除策略模版切面进入成功】");
if (result == 0 || StringUtils.isEmpty(templateId)) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
// 从ThreadLocal中获取删除前的对象
EmsStrategyTemp strategyTemp = beforeDeleteThreadLocal.get();
String siteId = "";
if (strategyTemp != null) {
siteId = strategyTemp.getSiteId();
}
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType, siteId);
try {
// 数据转换
Map<String, Object> idMap = new HashMap<>();
idMap.put("templateId", templateId); // 手动将参数值映射到"id"字段
String content = JSON.toJSONString(idMap);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
// 构建同步信息
private MqttSyncLog createMessageObject(String operateType, String siteId) {
MqttSyncLog message = new MqttSyncLog();
message.setSyncId(UUID.randomUUID().toString());
message.setOperateType(operateType);
message.setTableName(TABLE_NAME);
message.setCreateTime(new Date());
message.setTopic(MQTT_TOPIC);
message.setStatus("SUCCESS");
message.setSyncObject("CLOUD");
message.setTarget(siteId);
return message;
}
// 从方法名判断操作类型示例insert→INSERTupdate→UPDATEdelete→DELETE
private String getOperateType(String methodName) {
if (methodName.startsWith("insert")) return "INSERT";
if (methodName.startsWith("stop")) return "STOP";
if (methodName.startsWith("update") || methodName.startsWith("stop")) return "UPDATE";
if (methodName.startsWith("delete")) return "DELETE";
return "UNKNOWN";
}
// 从方法参数提取数据示例若参数是实体类转成Map
private Map<String, Object> extractDataFromParams(Object[] args) {
// 实际需反射获取实体类的字段和值如id、name等
Map<String, Object> data = new HashMap<>();
if (args == null || args.length == 0) {
return data;
}
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg == null) {
continue; // 跳过null参数
}
// 处理基本类型/包装类/字符串直接作为值存入key为"param0"、"param1"等)
if (isBasicType(arg.getClass())) {
String key = "param" + i; // 基本类型参数用"param0"、"param1"作为key
data.put(key, arg);
} else {
Map<String, Object> beanMap = beanToMap(arg);
data.putAll(beanMap); // 合并实体类的字段到结果Map
}
}
return data;
}
/**
* 判断是否为基本类型或包装类或字符串
*/
private boolean isBasicType(Class<?> clazz) {
return clazz.isPrimitive() // 基本类型int、long、boolean等
|| clazz == String.class // 字符串
|| Number.class.isAssignableFrom(clazz) // 数字包装类Integer、Long等
|| clazz == Boolean.class; // 布尔包装类
}
/**
* 将实体类转换为Map字段名为key字段值为value
*/
private Map<String, Object> beanToMap(Object bean) {
Map<String, Object> map = new HashMap<>();
if (bean == null) {
return map;
}
// 方式1使用BeanMap简洁高效
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(key.toString(), beanMap.get(key));
}
return map;
}
// 在方法中转换
public String convertEntityToJson(EmsStrategyRunning insertEntity) throws Exception {
if (insertEntity == null) {
return null; // 空对象返回空JSON
}
// 将实体类转换为JSON字符串
return objectMapper.writeValueAsString(insertEntity);
}
}

View File

@ -0,0 +1,241 @@
package com.xzzn.framework.aspectj;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.common.utils.bean.BeanUtils;
import com.xzzn.ems.domain.EmsStrategy;
import com.xzzn.ems.domain.EmsStrategyRunning;
import com.xzzn.ems.domain.EmsStrategyTimeConfig;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.domain.vo.SyncStrategyTimeConfigVo;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component;
/**
* 策略时间配置同步
* 云端 - 本地
*/
@Aspect
@Component
public class StrategyTimeConfigSyncAspect {
private static final Logger logger = LoggerFactory.getLogger(StrategyTimeConfigSyncAspect.class);
@Autowired
private MqttPublisher mqttPublisher;
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String MQTT_TOPIC = "EMS_STRATEGY_UP";
private static final String TABLE_NAME = "ems_strategy_temp";
@Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
@Autowired
private MqttSyncLogMapper mqttSyncLogMapper;
@Autowired
private EmsStrategyMapper emsStrategyMapper;
@Autowired
private EmsStrategyTempMapper emsStrategyTempMapper;
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper.insertEmsStrategyTimeConfig(..)) && args(insertEntity)) ")
public void insertPointCut(EmsStrategyTimeConfig insertEntity) {
logger.info("【新增策略模版时间配置切面】StrategyTimeConfigSyncAspect 被实例化");
}
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper.updateEmsStrategyTimeConfig(..)) && args(updateEntity)) ")
public void updatePointCut(EmsStrategyTimeConfig updateEntity) {
logger.info("【更新策略模版时间配置切面】StrategyTimeConfigSyncAspect 被实例化");
}
// 方法执行成功后发布同步消息
@AfterReturning(pointcut = "insertPointCut(insertEntity)", returning = "result")
public void afterInsert(JoinPoint joinPoint, EmsStrategyTimeConfig insertEntity, Integer result) {
logger.info("【新增策略模版时间切面进入成功】");
if (result == 0 || insertEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
String siteId = insertEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType, siteId);
try {
// 校验策略id是否存在
Long strategyId = insertEntity.getStrategyId();
if (strategyId == null) {
return;
}
// 数据转换
SyncStrategyTimeConfigVo timeConfigVo = convertEntity(insertEntity);
String content = objectMapper.writeValueAsString(timeConfigVo);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
private SyncStrategyTimeConfigVo convertEntity(EmsStrategyTimeConfig insertEntity) {
SyncStrategyTimeConfigVo timeConfigVo = new SyncStrategyTimeConfigVo();
BeanUtils.copyProperties(insertEntity, timeConfigVo);
EmsStrategy strategy = emsStrategyMapper.selectEmsStrategyById(insertEntity.getStrategyId());
if (strategy != null) {
timeConfigVo.setStrategyName(strategy.getStrategyName());
timeConfigVo.setStrategyType(strategy.getStrategyType());
}
return timeConfigVo;
}
@AfterReturning(pointcut = "updatePointCut(updateEntity)", returning = "result")
public void afterUpdate(JoinPoint joinPoint, EmsStrategyTimeConfig updateEntity, Integer result) {
logger.info("【删除策略模版切面进入成功】");
if (result == 0 || updateEntity == null) {
return;
}
// 校验是否配置监听topic-监听则不发布
String topic = emsMqttTopicConfigMapper.checkTopicIsExist(MQTT_TOPIC);
if (!StringUtils.isEmpty(topic)) {
return;
}
// 解析方法名获取操作类型INSERT/UPDATE/DELETE和表名
String methodName = joinPoint.getSignature().getName();
String operateType = getOperateType(methodName);
String siteId = updateEntity.getSiteId();
// 构建日志同步消息
MqttSyncLog message = createMessageObject(operateType, siteId);
try {
// 数据转换
SyncStrategyTimeConfigVo timeConfigVo = convertEntity(updateEntity);
String content = objectMapper.writeValueAsString(timeConfigVo);
message.setContent(content);
// 发布到MQTT主题
mqttPublisher.publish(MQTT_TOPIC, objectMapper.writeValueAsString(message), 1);
} catch (Exception e) {
message.setStatus("FAIL");
message.setErrorMsg(e.getMessage());
}
// 存储同步信息
mqttSyncLogMapper.insertMqttSyncLog(message);
}
// 构建同步信息
private MqttSyncLog createMessageObject(String operateType, String siteId) {
MqttSyncLog message = new MqttSyncLog();
message.setSyncId(UUID.randomUUID().toString());
message.setOperateType(operateType);
message.setTableName(TABLE_NAME);
message.setCreateTime(new Date());
message.setTopic(MQTT_TOPIC);
message.setStatus("SUCCESS");
message.setSyncObject("CLOUD");
message.setTarget(siteId);
return message;
}
// 从方法名判断操作类型示例insert→INSERTupdate→UPDATEdelete→DELETE
private String getOperateType(String methodName) {
if (methodName.startsWith("insert")) return "INSERT";
if (methodName.startsWith("stop")) return "STOP";
if (methodName.startsWith("update") || methodName.startsWith("stop")) return "UPDATE";
if (methodName.startsWith("delete")) return "DELETE";
return "UNKNOWN";
}
// 从方法参数提取数据示例若参数是实体类转成Map
private Map<String, Object> extractDataFromParams(Object[] args) {
// 实际需反射获取实体类的字段和值如id、name等
Map<String, Object> data = new HashMap<>();
if (args == null || args.length == 0) {
return data;
}
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg == null) {
continue; // 跳过null参数
}
// 处理基本类型/包装类/字符串直接作为值存入key为"param0"、"param1"等)
if (isBasicType(arg.getClass())) {
String key = "param" + i; // 基本类型参数用"param0"、"param1"作为key
data.put(key, arg);
} else {
Map<String, Object> beanMap = beanToMap(arg);
data.putAll(beanMap); // 合并实体类的字段到结果Map
}
}
return data;
}
/**
* 判断是否为基本类型或包装类或字符串
*/
private boolean isBasicType(Class<?> clazz) {
return clazz.isPrimitive() // 基本类型int、long、boolean等
|| clazz == String.class // 字符串
|| Number.class.isAssignableFrom(clazz) // 数字包装类Integer、Long等
|| clazz == Boolean.class; // 布尔包装类
}
/**
* 将实体类转换为Map字段名为key字段值为value
*/
private Map<String, Object> beanToMap(Object bean) {
Map<String, Object> map = new HashMap<>();
if (bean == null) {
return map;
}
// 方式1使用BeanMap简洁高效
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(key.toString(), beanMap.get(key));
}
return map;
}
// 在方法中转换
public String convertEntityToJson(EmsStrategyRunning insertEntity) throws Exception {
if (insertEntity == null) {
return null; // 空对象返回空JSON
}
// 将实体类转换为JSON字符串
return objectMapper.writeValueAsString(insertEntity);
}
}

View File

@ -13,8 +13,9 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
* @author xzzn * @author xzzn
*/ */
@Configuration @Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问 // proxyTargetClass = true表示使用cglib代理false表示jdk动态代理
@EnableAspectJAutoProxy(exposeProxy = true) // exposeProxy = true表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
// 指定要扫描的Mapper类的包的路径 // 指定要扫描的Mapper类的包的路径
@MapperScan("com.xzzn.**.mapper") @MapperScan("com.xzzn.**.mapper")
public class ApplicationConfig public class ApplicationConfig

View File

@ -0,0 +1,49 @@
/*
package com.xzzn.framework.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("modbusTaskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(300);
executor.setThreadNamePrefix("ModbusPoller-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
*/
/**
* 策略下方定时任务
*//*
*/
/*@Bean("strategyTaskExecutor")
public Executor strategyTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(300);
executor.setThreadNamePrefix("StrategyPoller-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}*//*
}
*/

View File

@ -0,0 +1,31 @@
package com.xzzn.framework.config;
import com.xzzn.framework.config.properties.MqttProperties;
import org.eclipse.paho.client.mqttv3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
@Configuration
public class MqttConfig {
private static final Logger logger = LoggerFactory.getLogger(MqttConfig.class);
@Resource
private MqttProperties mqttProperties;
@Bean
public MqttConnectOptions mqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{mqttProperties.getBrokerUrl()});
if (!mqttProperties.getUsername().isEmpty()) options.setUserName(mqttProperties.getUsername());
if (!mqttProperties.getPassword().isEmpty()) options.setPassword(mqttProperties.getPassword().toCharArray());
options.setConnectionTimeout(mqttProperties.getConnectionTimeout());
options.setKeepAliveInterval(mqttProperties.getKeepAliveInterval());
options.setAutomaticReconnect(mqttProperties.isAutomaticReconnect());
options.setCleanSession(true);
return options;
}
}

View File

@ -0,0 +1,84 @@
package com.xzzn.framework.config.properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MqttProperties {
@Value("${mqtt.broker.url}")
private String brokerUrl;
@Value("${mqtt.client.id:}")
private String clientId;
@Value("${mqtt.username:}")
private String username;
@Value("${mqtt.password:}")
private String password;
@Value("${mqtt.connection-timeout:10}")
private int connectionTimeout;
@Value("${mqtt.keep-alive-interval:60}")
private int keepAliveInterval;
@Value("${mqtt.automatic-reconnect:true}")
private boolean automaticReconnect;
public String getBrokerUrl() {
return brokerUrl;
}
public void setBrokerUrl(String brokerUrl) {
this.brokerUrl = brokerUrl;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public int getKeepAliveInterval() {
return keepAliveInterval;
}
public void setKeepAliveInterval(int keepAliveInterval) {
this.keepAliveInterval = keepAliveInterval;
}
public boolean isAutomaticReconnect() {
return automaticReconnect;
}
public void setAutomaticReconnect(boolean automaticReconnect) {
this.automaticReconnect = automaticReconnect;
}
}

View File

@ -0,0 +1,231 @@
package com.xzzn.framework.manager;
import com.xzzn.ems.service.IEmsAlarmRecordsService;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@Component
public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, MqttCallbackExtended {
private static final Logger log = LoggerFactory.getLogger(MqttLifecycleManager.class);
private static final long RECONNECT_DELAY_SECONDS = 5;
private final MqttConnectOptions connectOptions;
private final IEmsAlarmRecordsService iEmsAlarmRecordsService;
private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r);
thread.setName("mqtt-reconnect");
thread.setDaemon(true);
return thread;
});
private final AtomicBoolean reconnectScheduled = new AtomicBoolean(false);
private volatile ScheduledFuture<?> reconnectFuture;
private MqttClient mqttClient;
private volatile boolean running = false;
// 存储订阅关系: topic -> (listener, qos)
private final ConcurrentHashMap<String, SubscriptionInfo> subscriptions = new ConcurrentHashMap<>();
@Autowired
public MqttLifecycleManager(MqttConnectOptions connectOptions, IEmsAlarmRecordsService iEmsAlarmRecordsService) {
this.connectOptions = connectOptions;
this.iEmsAlarmRecordsService = iEmsAlarmRecordsService;
}
// Spring Boot 启动完成后执行
@Override
public void run(ApplicationArguments args) throws Exception {
start();
}
@Override
public void start() {
if (running) return;
try {
String prefix = connectOptions.getUserName() == null || connectOptions.getUserName().isEmpty()
? "mqtt-client" : connectOptions.getUserName();
String clientId = prefix + "-" + System.currentTimeMillis();
mqttClient = new MqttClient(
connectOptions.getServerURIs()[0],
clientId,
new MemoryPersistence()
);
mqttClient.setCallback(this);
mqttClient.connect(connectOptions);
resubscribeAll();
running = true;
log.info("MQTT client connected to: {}", connectOptions.getServerURIs()[0]);
} catch (MqttException e) {
running = false;
log.error("MQTT connection failed: {}", e.getMessage(), e);
scheduleReconnect();
}
}
@Override
public void stop() {
cancelReconnectTask();
if (mqttClient != null && mqttClient.isConnected()) {
try {
mqttClient.disconnect();
mqttClient.close();
} catch (MqttException e) {
log.warn("Error disconnecting MQTT client: {}", e.getMessage(), e);
}
}
reconnectExecutor.shutdownNow();
running = false;
}
@Override
public boolean isRunning() {
return running;
}
// MQTT 回调方法
@Override
public void connectionLost(Throwable cause) {
log.warn("MQTT connection lost: {}", cause == null ? "unknown" : cause.getMessage(), cause);
running = false;
scheduleReconnect();
}
@Override
public void connectComplete(boolean reconnect, String serverURI) {
running = true;
cancelReconnectTask();
log.info("MQTT connection complete, reconnect: {}, serverURI: {}", reconnect, serverURI);
resubscribeAll();
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
SubscriptionInfo info = subscriptions.get(topic);
if (info != null && info.getListener() != null) {
info.getListener().messageArrived(topic, message);
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// 消息发布完成处理
}
// 订阅方法
public void subscribe(String topic, int qos, IMqttMessageListener listener) {
try {
if (mqttClient != null && mqttClient.isConnected()) {
mqttClient.subscribe(topic, qos);
log.info("MQTT subscribe success, topic: {}, qos: {}", topic, qos);
} else {
log.warn("MQTT subscribe deferred, client not connected, topic: {}", topic);
}
subscriptions.put(topic, new SubscriptionInfo(listener, qos));
} catch (MqttException e) {
log.error("Subscribe failed, topic: {}, err: {}", topic, e.getMessage(), e);
iEmsAlarmRecordsService.addSubFailedAlarmRecord(topic);
scheduleReconnect();
}
iEmsAlarmRecordsService.checkFailedRecord(topic);
}
// 发布方法
public void publish(String topic, String payload, int qos) throws MqttException {
if (mqttClient != null && mqttClient.isConnected()) {
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(qos);
mqttClient.publish(topic, message);
} else {
throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
}
}
// 重新订阅所有主题
private void resubscribeAll() {
if (mqttClient == null || !mqttClient.isConnected()) return;
subscriptions.forEach((topic, info) -> {
try {
mqttClient.subscribe(topic, info.getQos());
log.info("MQTT resubscribe success, topic: {}, qos: {}", topic, info.getQos());
} catch (MqttException e) {
log.error("Resubscribe failed for topic: {}, err: {}", topic, e.getMessage(), e);
}
});
}
private void scheduleReconnect() {
if (mqttClient == null || reconnectExecutor.isShutdown()) {
return;
}
if (!reconnectScheduled.compareAndSet(false, true)) {
return;
}
reconnectFuture = reconnectExecutor.scheduleWithFixedDelay(() -> {
if (mqttClient == null) {
cancelReconnectTask();
return;
}
if (mqttClient.isConnected()) {
cancelReconnectTask();
return;
}
try {
log.info("MQTT reconnecting...");
mqttClient.connect(connectOptions);
running = true;
cancelReconnectTask();
resubscribeAll();
log.info("MQTT reconnect success.");
} catch (MqttException e) {
running = false;
log.warn("MQTT reconnect failed: {}", e.getMessage());
}
}, RECONNECT_DELAY_SECONDS, RECONNECT_DELAY_SECONDS, TimeUnit.SECONDS);
}
private void cancelReconnectTask() {
reconnectScheduled.set(false);
ScheduledFuture<?> future = reconnectFuture;
if (future != null && !future.isCancelled()) {
future.cancel(false);
}
}
// 订阅信息内部类
private static class SubscriptionInfo {
private final IMqttMessageListener listener;
private final int qos;
public SubscriptionInfo(IMqttMessageListener listener, int qos) {
this.listener = listener;
this.qos = qos;
}
public IMqttMessageListener getListener() {
return listener;
}
public int getQos() {
return qos;
}
}
}

View File

@ -0,0 +1,8 @@
package com.xzzn.framework.web.service;
import org.eclipse.paho.client.mqttv3.MqttException;
public interface MqttPublisher {
void publish(String topic, String message) throws MqttException;
void publish(String topic, String message, int qos) throws MqttException;
}

View File

@ -0,0 +1,7 @@
package com.xzzn.framework.web.service;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
public interface MqttSubscriber {
void subscribe(String topic, int qos, IMqttMessageListener listener);
}

View File

@ -1,7 +1,6 @@
package com.xzzn.generator.controller; package com.xzzn.generator.controller;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -18,10 +17,6 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.xzzn.common.annotation.Log; import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController; import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult; import com.xzzn.common.core.domain.AjaxResult;
@ -29,7 +24,6 @@ import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.core.text.Convert; import com.xzzn.common.core.text.Convert;
import com.xzzn.common.enums.BusinessType; import com.xzzn.common.enums.BusinessType;
import com.xzzn.common.utils.SecurityUtils; import com.xzzn.common.utils.SecurityUtils;
import com.xzzn.common.utils.sql.SqlUtil;
import com.xzzn.generator.config.GenConfig; import com.xzzn.generator.config.GenConfig;
import com.xzzn.generator.domain.GenTable; import com.xzzn.generator.domain.GenTable;
import com.xzzn.generator.domain.GenTableColumn; import com.xzzn.generator.domain.GenTableColumn;
@ -121,43 +115,6 @@ public class GenController extends BaseController
return success(); return success();
} }
/**
* 创建表结构(保存)
*/
@PreAuthorize("@ss.hasRole('admin')")
@Log(title = "创建表", businessType = BusinessType.OTHER)
@PostMapping("/createTable")
public AjaxResult createTableSave(String sql)
{
try
{
SqlUtil.filterKeyword(sql);
List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql);
List<String> tableNames = new ArrayList<>();
for (SQLStatement sqlStatement : sqlStatements)
{
if (sqlStatement instanceof MySqlCreateTableStatement)
{
MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement;
if (genTableService.createTable(createTableStatement.toString()))
{
String tableName = createTableStatement.getTableName().replaceAll("`", "");
tableNames.add(tableName);
}
}
}
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()]));
String operName = SecurityUtils.getUsername();
genTableService.importGenTable(tableList, operName);
return AjaxResult.success();
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
return AjaxResult.error("创建表结构异常");
}
}
/** /**
* 修改保存代码生成业务 * 修改保存代码生成业务
*/ */
@ -260,4 +217,4 @@ public class GenController extends BaseController
response.setContentType("application/octet-stream; charset=UTF-8"); response.setContentType("application/octet-stream; charset=UTF-8");
IOUtils.write(data, response.getOutputStream()); IOUtils.write(data, response.getOutputStream());
} }
} }

View File

@ -81,11 +81,4 @@ public interface GenTableMapper
*/ */
public int deleteGenTableByIds(Long[] ids); public int deleteGenTableByIds(Long[] ids);
/**
* 创建表
*
* @param sql 表结构
* @return 结果
*/
public int createTable(String sql);
} }

View File

@ -149,18 +149,6 @@ public class GenTableServiceImpl implements IGenTableService
genTableColumnMapper.deleteGenTableColumnByIds(tableIds); genTableColumnMapper.deleteGenTableColumnByIds(tableIds);
} }
/**
* 创建表
*
* @param sql 创建表语句
* @return 结果
*/
@Override
public boolean createTable(String sql)
{
return genTableMapper.createTable(sql) == 0;
}
/** /**
* 导入表结构 * 导入表结构
* *
@ -528,4 +516,4 @@ public class GenTableServiceImpl implements IGenTableService
} }
return genPath + File.separator + VelocityUtils.getFileName(template, table); return genPath + File.separator + VelocityUtils.getFileName(template, table);
} }
} }

View File

@ -66,14 +66,6 @@ public interface IGenTableService
*/ */
public void deleteGenTableByIds(Long[] tableIds); public void deleteGenTableByIds(Long[] tableIds);
/**
* 创建表
*
* @param sql 创建表语句
* @return 结果
*/
public boolean createTable(String sql);
/** /**
* 导入表结构 * 导入表结构
* *

View File

@ -171,10 +171,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
) )
</insert> </insert>
<update id="createTable">
${sql}
</update>
<update id="updateGenTable" parameterType="GenTable"> <update id="updateGenTable" parameterType="GenTable">
update gen_table update gen_table
<set> <set>
@ -207,4 +203,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach> </foreach>
</delete> </delete>
</mapper> </mapper>

View File

@ -34,6 +34,10 @@
<groupId>com.xzzn</groupId> <groupId>com.xzzn</groupId>
<artifactId>ems-common</artifactId> <artifactId>ems-common</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.xzzn</groupId>
<artifactId>ems-framework</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@ -0,0 +1,33 @@
package com.xzzn.quartz.config;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.PreDestroy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Modbus操作执行器配置
* 所有Modbus读写操作必须通过此单线程执行器串行执行避免并发访问导致通讯故障
*/
@Configuration
public class ModbusExecutorConfig {
private final ExecutorService modbusExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "modbus-io-thread");
t.setDaemon(false);
return t;
});
@Bean(name = "modbusExecutor")
public ExecutorService modbusExecutor() {
return modbusExecutor;
}
@PreDestroy
public void shutdown() {
modbusExecutor.shutdown();
}
}

View File

@ -0,0 +1,34 @@
package com.xzzn.quartz.config;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTask {
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
private final Map<String, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>();
public void startTask(String deviceId, Runnable task, long period) {
stopTask(deviceId); // 如果已有同ID任务在运行先停止
ScheduledFuture<?> future = executor.scheduleAtFixedRate(task, 0, period, TimeUnit.MILLISECONDS);
futureMap.put(deviceId, future);
}
public void stopTask(String deviceId) {
ScheduledFuture<?> future = futureMap.get(deviceId);
if (future != null && !future.isDone()) {
future.cancel(true);
}
futureMap.remove(deviceId);
}
}

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