Compare commits

...

51 Commits

Author SHA1 Message Date
  yinhuaiwei aa593aecbb feat: eqpbookChannel表新增useMode字段的汇聚 6 days ago
  yinhuaiwei adf227e030 feat: eqpbookChannel表新增useMode字段用于区分摄像头设备类型 1 week ago
  yinhuaiwei cbb216aa38 feat: 当登录ip值为空或*时,允许任意ip进行登录 1 week ago
  yinhuaiwei 666f11175b feat: 部门树和菜单树新增status字段,表示启用/禁用 2 weeks ago
  yinhuaiwei 6ad6dd9fb5 feat: 汇聚agg_station新增order_num自定义排序 2 weeks ago
  yinhuaiwei 932d5b0824 feat: 新增中恒token登录后的“动环”相关角色同步 2 weeks ago
  WangGuangYuan 419d4e2b36 Merge branch 'feature/agg' of http://git.ht-atia.cn/qinyanlei/inspect into feature/agg 2 weeks ago
  WangGuangYuan 9f42c6fc59 perf: 汇聚层访问站端改用 HTTP 连接池 2 weeks ago
  yinhuaiwei ee5c111a47 fix:修复三方登录异常;同步三方aollowIp设为*;对null值做处理 2 weeks ago
  yinhuaiwei 22ad366412 feat: 定时同步中恒用户相关信息,并实现中恒token登录 2 weeks ago
  yinhuaiwei aa604167e0 fix: 修复汇聚缺陷记录按时间查询;暂时不统计轻微缺陷数量 2 weeks ago
  yinhuaiwei 2811ed7568 feat: 新增channel树的deviceSource筛选;新增对channel表capabilities字段的汇聚;新增汇聚area/eqpbook/channel表的order_num字段自定义排序 3 weeks ago
  yinhuaiwei 8f69b1e95b feat: 汇聚端 nginx 按 stationCode 将前端对图片、文件、视频流的请求反代到各站端;实时流的启停由汇聚直接调用站端接口 3 weeks ago
  yinhuaiwei 3d4ca6ad4a feat: 更新gitignore 1 month ago
  yinhuaiwei d518479bb4 汇聚代码第一次提交 1 month ago
  yinhuaiwei 3db44ddabc feat: 表计meter算法支持对读数“正常”的解析 2 months ago
  wangguangyuan c25a8d0354 fix: 修复报告上传消息无限重投递,超过 3 次直接丢弃 2 months ago
  wangguangyuan d5b7c89d98 Merge branch 'yanyuan' of http://git.ht-atia.cn/qinyanlei/inspect into yanyuan 2 months ago
  wangguangyuan 8b7b8c340f feat: 缺陷记录添加算法筛选,按算法名精确匹配 2 months ago
  yinhuaiwei 5667eaea71 feat: 新增分析流程完结字段completed用于数据上送 2 months ago
  wangguangyuan ff5dbf20b0 feat: 缺陷记录添加下拉筛选,按缺陷描述精确匹配 2 months ago
  yinhuaiwei 2fde923bf6 temp: 临时屏蔽 RequiresPermissions 注解 2 months ago
  yinhuaiwei 86055b2281 feat: 新增通道查询返回台账名称 2 months ago
  yinhuaiwei c7e0677b81 feat: 新增接口用于明文用户密码获取token 2 months ago
  wangguangyuan f7d4b63c2e Merge branch 'yanyuan' of http://git.ht-atia.cn/qinyanlei/inspect into yanyuan 2 months ago
  yinhuaiwei 40854cc240 fix: 修复修改用户信息时会将用户密码同步修改 2 months ago
  wangguangyuan 166ccb8b2b feat: 缺陷记录添加筛选条件 2 months ago
  yinhuaiwei b5ec64abe0 feat: 点位数据分析完成时通过WS上送 2 months ago
  wangguangyuan 6313f95ded fix(redis): 按分钟执行的间隔任务不再生成窗口外的策略 key 2 months ago
  wangguangyuan de77ada07a fix(redis): 生产路径禁止使用 KEYS,改用 SCAN,修复 Redis 阻塞导致巡检服务不可用 2 months ago
  yinhuaiwei 0aef3b2e15 feat: 取消数据查询权限限制 2 months ago
  wangguangyuan f37f7bbecc fix: 动环巡检报告目录调整 2 months ago
  wangguangyuan fef4796256 fix: 动环巡检报告排版对齐机器人巡检报告模板 2 months ago
  yinhuaiwei 8a19df5709 refactor: 优化盐源ws推送,支持任意消息格式 2 months ago
  yinhuaiwei aa54054e76 feat: 临时屏蔽接口所需权限 2 months ago
  WangGuangYuan c81de07f7a fix:巡视报告格式,字体修改 2 months ago
  wangguangyuan 443d30bf02 fix:动环报告合并及归档推送逻辑修改,新增了inspection_report_task表 2 months ago
  wangguangyuan a2649203c3 fix:报告推送任务id修改,新增自动归档开关与推送开关 2 months ago
  wangguangyuan b88c984f1c feat:雨刮和框选3D定位 2 months ago
  yinhuaiwei c10550aaef Merge branch 'yanyuan' of https://git.ht-atia.cn/qinyanlei/inspect into yanyuan 2 months ago
  yinhuaiwei 5ac0de509c fix: 修复因计划任务过多判断,导致任务错过下发时间未执行 2 months ago
  wangguangyuan e8a501d488 feat:巡视报告标题字体黑体 4号,标题段落首行缩进1字符 2 months ago
  wangguangyuan d07dda28b5 feat:缺陷汇总接口,相机通道接口 2 months ago
  wangguangyuan c87a45671c feat:盐源新版巡视报告字体嵌入,及http推送外部接口 2 months ago
  wangguangyuan 57e78d5c3f feat:盐源新版巡视报告,及自动归档,websocket推送 3 months ago
  yinhuaiwei 7bc91a250c feat: 新增getToken接口,用于第三方调用接口鉴权 3 months ago
  yinhuaiwei 6aaae83f4f feat: 新增盐源ws接口(通过AlarmPublisher发送ws) 3 months ago
  wangguangyuan 91706e3913 fix:巡视报告异常捕获 3 months ago
  wangguangyuan 57c6724f4d feat:视频预览,预置位操作,云台控制,抓拍,录像下载 3 months ago
  wangguangyuan c9a56d778a feat:1.历史回放接口,2.台账接口修改 3 months ago
  yinhuaiwei 7403bcd61f 第一次提交:1.新增inspect-main-api模块,用于开发盐源新特性接口;2.启动全局异常捕获器,对服务异常统一封装 3 months ago
268 changed files with 17298 additions and 682 deletions
Unified View
  1. +1
    -0
      .gitignore
  2. +35
    -3
      inspect-admin/src/main/java/com/inspect/admin/controller/TokenController.java
  3. +24
    -0
      inspect-admin/src/main/java/com/inspect/admin/feign/RemoteExtAuthService.java
  4. +30
    -0
      inspect-admin/src/main/java/com/inspect/admin/feign/RemoteExtUserService.java
  5. +37
    -0
      inspect-admin/src/main/java/com/inspect/admin/feign/domain/JoinbrightUser.java
  6. +26
    -0
      inspect-admin/src/main/java/com/inspect/admin/feign/factory/RemoteExtAuthFallbackFactory.java
  7. +31
    -0
      inspect-admin/src/main/java/com/inspect/admin/feign/factory/RemoteExtUserFallbackFactory.java
  8. +152
    -0
      inspect-admin/src/main/java/com/inspect/admin/service/JoinbrightLoginService.java
  9. +28
    -2
      inspect-admin/src/main/java/com/inspect/admin/service/SysLoginService.java
  10. +16
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/ExtAuthConstants.java
  11. +1
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/RedisConst.java
  12. +1
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/TokenConst.java
  13. +2
    -2
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgCategoryEnum.java
  14. +2
    -2
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AnalysisType.java
  15. +21
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/DefectLevel.java
  16. +22
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/UserType.java
  17. +1
    -1
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/sftp/SftpClient.java
  18. +110
    -3
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/utils/HttpClientUtils.java
  19. +4
    -0
      inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/web/domain/AjaxResult.java
  20. +51
    -0
      inspect-base/inspect-base-redis/src/main/java/com/inspect/base/redis/service/RedisService.java
  21. +10
    -0
      inspect-base/inspect-base-system/src/main/java/com/inspect/system/base/domain/SysDept.java
  22. +10
    -0
      inspect-base/inspect-base-system/src/main/java/com/inspect/system/base/domain/SysRole.java
  23. +10
    -0
      inspect-base/inspect-base-system/src/main/java/com/inspect/system/base/domain/SysUser.java
  24. +4
    -3
      inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/aspect/PreAuthorizeAspect.java
  25. +1
    -2
      inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/handler/GlobalExceptionHandler.java
  26. +1
    -0
      inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/service/TokenService.java
  27. +15
    -0
      inspect-ivs/src/main/java/com/inspect/ivs/constant/IvsConst.java
  28. +33
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsControlController.java
  29. +22
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsDeviceController.java
  30. +45
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsPlaybackController.java
  31. +14
    -1
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsRecordController.java
  32. +30
    -3
      inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsStreamController.java
  33. +102
    -0
      inspect-ivs/src/main/java/com/inspect/ivs/service/HlsPlaybackComponent.java
  34. +3
    -0
      inspect-ivs/src/main/resources/bootstrap.yml
  35. +23
    -0
      inspect-job/src/main/java/com/inspect/job/client/ExtOrgSyncClient.java
  36. +26
    -0
      inspect-job/src/main/java/com/inspect/job/client/ReportArchiveClient.java
  37. +63
    -0
      inspect-job/src/main/java/com/inspect/job/task/ExtOrgSyncTask.java
  38. +15
    -15
      inspect-job/src/main/java/com/inspect/job/task/JobMainTask.java
  39. +81
    -0
      inspect-job/src/main/java/com/inspect/job/task/ReportArchiveScheduleTask.java
  40. +62
    -0
      inspect-main/inspect-main-agg/pom.xml
  41. +20
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/AggregationApplication.java
  42. +28
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggCalendarEvent.java
  43. +180
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarGenerator.java
  44. +340
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarService.java
  45. +111
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/AggProperties.java
  46. +83
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/RestTemplateConfig.java
  47. +19
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/SyncDefaults.java
  48. +38
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/dto/AggMonitorPatrolPointVo.java
  49. +15
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/dto/VideoRequest.java
  50. +13
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggAlgSubtype.java
  51. +26
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggAnalysisDefectMonth.java
  52. +43
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataArea.java
  53. +42
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataDevice.java
  54. +37
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataDevicetype.java
  55. +42
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataEqpbook.java
  56. +47
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataEqpbookChannel.java
  57. +44
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataPatrolpoint.java
  58. +64
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggDefectRecord.java
  59. +50
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggInspectionReport.java
  60. +65
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggPatrolTask.java
  61. +49
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggPatrolTaskStatus.java
  62. +45
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggResultAnalysis.java
  63. +33
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggStation.java
  64. +27
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggSyncLog.java
  65. +37
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/enums/AlgCategory.java
  66. +25
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggAnalysisDefectMonthMapper.java
  67. +30
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataAreaMapper.java
  68. +26
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataDeviceMapper.java
  69. +26
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataDevicetypeMapper.java
  70. +28
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataEqpbookChannelMapper.java
  71. +26
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataEqpbookMapper.java
  72. +31
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataPatrolpointMapper.java
  73. +26
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggDefectRecordMapper.java
  74. +26
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggInspectionReportMapper.java
  75. +31
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggPatrolTaskMapper.java
  76. +46
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggPatrolTaskStatusMapper.java
  77. +22
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggResultAnalysisMapper.java
  78. +24
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggStationMapper.java
  79. +14
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggSyncLogMapper.java
  80. +12
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/BasedataAlgSubtypeMapper.java
  81. +61
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/scheduler/BaseSyncScheduler.java
  82. +28
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/scheduler/DefectRecordSyncScheduler.java
  83. +53
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/scheduler/DynamicSyncScheduler.java
  84. +33
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/scheduler/ResultAnalysisSyncScheduler.java
  85. +100
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/AggAnalysisDefectMonthSyncService.java
  86. +144
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/AggDeviceTreeService.java
  87. +295
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/AggStatisticService.java
  88. +99
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/AreaSyncService.java
  89. +102
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/ChannelSyncService.java
  90. +185
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/DefectRecordSyncService.java
  91. +103
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/DeviceSyncService.java
  92. +102
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/DevicetypeSyncService.java
  93. +103
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/EqpbookSyncService.java
  94. +280
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/EqpbookTreeService.java
  95. +104
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/InspectionReportSyncService.java
  96. +120
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/PatrolTaskSyncService.java
  97. +99
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/PointSyncService.java
  98. +130
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/ResultAnalysisSyncService.java
  99. +47
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/StationRegistry.java
  100. +147
    -0
      inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/TaskExecSyncService.java

+ 1
- 0
.gitignore View File

@ -45,3 +45,4 @@ nbdist/
!*/build/*.java !*/build/*.java
!*/build/*.html !*/build/*.html
!*/build/*.xml !*/build/*.xml
/docs/

+ 35
- 3
inspect-admin/src/main/java/com/inspect/admin/controller/TokenController.java View File

@ -2,9 +2,11 @@ package com.inspect.admin.controller;
import com.inspect.admin.form.LoginBody; import com.inspect.admin.form.LoginBody;
import com.inspect.admin.form.RegisterBody; import com.inspect.admin.form.RegisterBody;
import com.inspect.admin.service.JoinbrightLoginService;
import com.inspect.admin.service.SysLoginService; import com.inspect.admin.service.SysLoginService;
import com.inspect.base.core.constant.TokenConst; import com.inspect.base.core.constant.TokenConst;
import com.inspect.base.core.domain.Response; import com.inspect.base.core.domain.Response;
import com.inspect.base.core.exception.ServiceException;
import com.inspect.base.core.utils.JwtUtils; import com.inspect.base.core.utils.JwtUtils;
import com.inspect.base.core.utils.StringUtils; import com.inspect.base.core.utils.StringUtils;
import com.inspect.common.security.auth.AuthUtil; import com.inspect.common.security.auth.AuthUtil;
@ -14,11 +16,13 @@ import com.inspect.system.base.model.LoginUser;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired; 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.RestController;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController @RestController
public class TokenController { public class TokenController {
@Autowired @Autowired
@ -27,6 +31,9 @@ public class TokenController {
@Autowired @Autowired
private SysLoginService sysLoginService; private SysLoginService sysLoginService;
@Autowired
private JoinbrightLoginService joinbrightLoginService;
@PostMapping({"login"}) @PostMapping({"login"})
public Response<?> login(@RequestBody LoginBody form, HttpServletRequest request) { public Response<?> login(@RequestBody LoginBody form, HttpServletRequest request) {
LoginUser userInfo = this.sysLoginService.login(form.getUsername(), form.getPassword(), request); LoginUser userInfo = this.sysLoginService.login(form.getUsername(), form.getPassword(), request);
@ -76,4 +83,29 @@ public class TokenController {
mapApi.put("value", "Bearer " + map.get(TokenConst.ACCESS_TOKEN)); mapApi.put("value", "Bearer " + map.get(TokenConst.ACCESS_TOKEN));
return Response.ok(mapApi, "操作成功"); return Response.ok(mapApi, "操作成功");
} }
@GetMapping({"/getToken"})
public Response<?> getToken(String username, String password, HttpServletRequest request) {
log.info("getToken username: {}, password: {}", username, password);
String md5Hex = DigestUtils.md5Hex(password);
LoginUser userInfo = this.sysLoginService.login(username, md5Hex, request);
Map<String, Object> map = this.tokenService.createToken(userInfo);
return Response.ok(map, "操作成功");
}
/**
* joinbright 页面集成单点登录。
* 前端在 header(优先)或 query 传入 joinbright-token,校验外部用户后签发本平台 token。
* 外部认证失败返回 4001(前端回登录页),平台账号停用 4003,同名内置账号冲突 4009,外部服务异常 5003。
*/
@GetMapping("/joinbrightLogin")
public Response<?> joinbrightLogin(HttpServletRequest request) {
try {
Map<String, Object> map = joinbrightLoginService.login(request);
return Response.ok(map, "操作成功");
} catch (ServiceException e) {
log.warn("joinbrightLogin 失败: code={}, msg={}", e.getCode(), e.getMessage());
return Response.fail(e.getCode() == null ? Response.FAIL : e.getCode(), e.getMessage());
}
}
} }

+ 24
- 0
inspect-admin/src/main/java/com/inspect/admin/feign/RemoteExtAuthService.java View File

@ -0,0 +1,24 @@
package com.inspect.admin.feign;
import com.inspect.admin.feign.domain.JoinbrightUser;
import com.inspect.admin.feign.factory.RemoteExtAuthFallbackFactory;
import com.inspect.base.core.constant.SecurityConstants;
import com.inspect.base.core.domain.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
/**
* 调用 inspect-main 内部接口,由 inspect-main 向外部盐源平台换取登录用户。
*/
@FeignClient(
contextId = "remoteExtAuthService",
value = "inspect-main",
fallbackFactory = RemoteExtAuthFallbackFactory.class
)
public interface RemoteExtAuthService {
@GetMapping("/inner/ext_auth/loginUser")
Response<JoinbrightUser> getLoginUser(@RequestHeader("joinbright-token") String joinbrightToken,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

+ 30
- 0
inspect-admin/src/main/java/com/inspect/admin/feign/RemoteExtUserService.java View File

@ -0,0 +1,30 @@
package com.inspect.admin.feign;
import com.inspect.admin.feign.domain.JoinbrightUser;
import com.inspect.admin.feign.factory.RemoteExtUserFallbackFactory;
import com.inspect.base.core.constant.SecurityConstants;
import com.inspect.base.core.web.domain.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 调用 dliip-system 的内部接口,为外部登录用户自动建号 / 绑定本地账号。
*/
@FeignClient(
contextId = "remoteExtUserService",
value = "inspect-management",
fallbackFactory = RemoteExtUserFallbackFactory.class
)
public interface RemoteExtUserService {
@PostMapping("/inner/ext_user/ensure")
AjaxResult ensureUser(@RequestBody JoinbrightUser user,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
@PostMapping("/inner/ext_user/syncRoles")
AjaxResult syncRoles(@RequestParam("username") String username,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

+ 37
- 0
inspect-admin/src/main/java/com/inspect/admin/feign/domain/JoinbrightUser.java View File

@ -0,0 +1,37 @@
package com.inspect.admin.feign.domain;
import lombok.Data;
import java.io.Serializable;
/**
* 内部接口 /inner/ext_auth/loginUser 返回的外部登录用户信息。
* 与 inspect-main-api 的 ExtLoginUser 按 JSON 字段约定保持一致。
*/
@Data
public class JoinbrightUser implements Serializable {
private static final long serialVersionUID = 1L;
/** 用户id(对应本地 sys_user.ext_id) */
private String id;
/** 账号 */
private String username;
/** 用户名 */
private String realName;
/** 账号状态编码,0201401 为激活 */
private String accountStatus;
/** 账号状态文本,如“激活” */
private String accountStatusText;
/** 所属部门id(对应本地 sys_dept.ext_id) */
private String deptId;
/** 所属部门名称 */
private String deptName;
/** 手机号 */
private String mobilePhone;
/** 邮箱 */
private String email;
/** 所属场站id */
private String boosterStationId;
/** 所属场站名称 */
private String boosterStationName;
}

+ 26
- 0
inspect-admin/src/main/java/com/inspect/admin/feign/factory/RemoteExtAuthFallbackFactory.java View File

@ -0,0 +1,26 @@
package com.inspect.admin.feign.factory;
import com.inspect.admin.feign.RemoteExtAuthService;
import com.inspect.admin.feign.domain.JoinbrightUser;
import com.inspect.base.core.constant.ExtAuthConstants;
import com.inspect.base.core.domain.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class RemoteExtAuthFallbackFactory implements FallbackFactory<RemoteExtAuthService> {
private static final Logger log = LoggerFactory.getLogger(RemoteExtAuthFallbackFactory.class);
@Override
public RemoteExtAuthService create(final Throwable throwable) {
log.error("[JOINBRIGHT] inspect-main 内部服务调用失败:{}", throwable.getMessage());
return new RemoteExtAuthService() {
@Override
public Response<JoinbrightUser> getLoginUser(String joinbrightToken, String source) {
return Response.fail(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部认证服务暂不可用,请稍后重试");
}
};
}
}

+ 31
- 0
inspect-admin/src/main/java/com/inspect/admin/feign/factory/RemoteExtUserFallbackFactory.java View File

@ -0,0 +1,31 @@
package com.inspect.admin.feign.factory;
import com.inspect.admin.feign.RemoteExtUserService;
import com.inspect.admin.feign.domain.JoinbrightUser;
import com.inspect.base.core.constant.ExtAuthConstants;
import com.inspect.base.core.web.domain.AjaxResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class RemoteExtUserFallbackFactory implements FallbackFactory<RemoteExtUserService> {
private static final Logger log = LoggerFactory.getLogger(RemoteExtUserFallbackFactory.class);
@Override
public RemoteExtUserService create(final Throwable throwable) {
log.error("[JOINBRIGHT] inspect-management 用户建号服务调用失败:{}", throwable.getMessage());
return new RemoteExtUserService() {
@Override
public AjaxResult ensureUser(JoinbrightUser user, String source) {
return AjaxResult.error(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部账号同步服务暂不可用,请稍后重试");
}
@Override
public AjaxResult syncRoles(String username, String source) {
return AjaxResult.error(ExtAuthConstants.SERVICE_UNAVAILABLE, "外部角色同步服务暂不可用");
}
};
}
}

+ 152
- 0
inspect-admin/src/main/java/com/inspect/admin/service/JoinbrightLoginService.java View File

@ -0,0 +1,152 @@
package com.inspect.admin.service;
import com.inspect.admin.feign.RemoteExtAuthService;
import com.inspect.admin.feign.RemoteExtUserService;
import com.inspect.admin.feign.domain.JoinbrightUser;
import com.inspect.base.core.constant.ExtAuthConstants;
import com.inspect.base.core.constant.SecurityConstants;
import com.inspect.base.core.domain.Response;
import com.inspect.base.core.exception.ServiceException;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.core.web.domain.AjaxResult;
import com.inspect.common.security.service.TokenService;
import com.inspect.system.base.model.LoginUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* joinbright 页面集成单点登录:
* 1. 前端在 header(优先)或 query 传入 joinbright-token;
* 2. 经 inspect-main 内部接口向外部平台 /ext_auth/getLoginUser 换取用户信息;
* 3. 外部认证失败或未返回用户名:返回 4001,前端回登录页,不再降级 guest/htjc;
* 4. 平台已有该账号:直接签发平台 token;平台没有:按外部数据自动建号(密码 abcd1234*、角色 common);
* 5. 同名本地账号按外部数据绑定并覆盖,内置账号 admin/guest/htjc 受保护,返回 4009。
*/
@Service
public class JoinbrightLoginService {
private static final Logger log = LoggerFactory.getLogger(JoinbrightLoginService.class);
private static final String JOINBRIGHT_TOKEN = "joinbright-token";
private static final String MSG_TOKEN_INVALID = "登录凭证无效,请重新登录";
private static final String MSG_SERVICE_UNAVAILABLE = "外部认证服务暂不可用,请稍后重试";
@Resource
private RemoteExtAuthService remoteExtAuthService;
@Resource
private RemoteExtUserService remoteExtUserService;
@Resource
private SysLoginService sysLoginService;
@Resource
private TokenService tokenService;
public Map<String, Object> login(HttpServletRequest request) {
String joinbrightToken = request.getParameter(JOINBRIGHT_TOKEN);
if (StringUtils.isEmpty(joinbrightToken)) {
throw new ServiceException(MSG_TOKEN_INVALID, ExtAuthConstants.TOKEN_INVALID);
}
JoinbrightUser externalUser = getExternalUser(joinbrightToken);
ensurePlatformUser(externalUser);
syncExternalRoles(externalUser.getUsername());
LoginUser loginUser = loadPlatformUser(externalUser.getUsername());
return tokenService.createToken(loginUser);
}
/**
* 调内部接口换取外部用户信息;任何失败都直接抛业务异常,由前端按错误码回登录页。
*/
private JoinbrightUser getExternalUser(String joinbrightToken) {
Response<JoinbrightUser> response;
try {
response = remoteExtAuthService.getLoginUser(joinbrightToken, SecurityConstants.INNER);
} catch (Exception e) {
log.error("[JOINBRIGHT] 调用外部认证服务异常", e);
throw new ServiceException(MSG_SERVICE_UNAVAILABLE, ExtAuthConstants.SERVICE_UNAVAILABLE);
}
if (response == null) {
log.warn("[JOINBRIGHT] 外部认证服务无响应");
throw new ServiceException(MSG_SERVICE_UNAVAILABLE, ExtAuthConstants.SERVICE_UNAVAILABLE);
}
if (response.getCode() == ExtAuthConstants.SERVICE_UNAVAILABLE) {
throw new ServiceException(defaultMessage(response.getMsg(), MSG_SERVICE_UNAVAILABLE),
ExtAuthConstants.SERVICE_UNAVAILABLE);
}
if (response.getCode() != Response.SUCCESS) {
log.warn("[JOINBRIGHT] 外部认证失败: code={}, msg={}", response.getCode(), response.getMsg());
throw new ServiceException(defaultMessage(response.getMsg(), MSG_TOKEN_INVALID),
ExtAuthConstants.TOKEN_INVALID);
}
JoinbrightUser user = response.getData();
if (user == null || StringUtils.isEmpty(user.getUsername())) {
log.warn("[JOINBRIGHT] 外部认证未返回用户名");
throw new ServiceException("未获取到外部用户信息,请重新登录", ExtAuthConstants.TOKEN_INVALID);
}
return user;
}
/**
* 平台用户不存在时自动建号,存在时由 dliip-system 按 ext_id 更新/绑定。
*/
private void ensurePlatformUser(JoinbrightUser externalUser) {
AjaxResult result;
try {
result = remoteExtUserService.ensureUser(externalUser, SecurityConstants.INNER);
} catch (Exception e) {
log.error("[JOINBRIGHT] 调用用户建号服务异常, username={}", externalUser.getUsername(), e);
throw new ServiceException("外部账号同步失败,请稍后重试", ExtAuthConstants.SERVICE_UNAVAILABLE);
}
if (result == null) {
throw new ServiceException("外部账号同步失败,请稍后重试", ExtAuthConstants.SERVICE_UNAVAILABLE);
}
int code = readCode(result);
if (code != Response.SUCCESS) {
String message = defaultMessage((String) result.get(AjaxResult.MSG_TAG), "外部账号同步失败,请稍后重试");
log.warn("[JOINBRIGHT] 用户建号失败: code={}, msg={}", code, message);
throw new ServiceException(message, code);
}
}
/**
* 登录时同步外部角色:失败只记录日志,不阻断登录(fail-open)。
*/
private void syncExternalRoles(String username) {
try {
AjaxResult result = remoteExtUserService.syncRoles(username, SecurityConstants.INNER);
if (result == null || readCode(result) != Response.SUCCESS) {
log.warn("[JOINBRIGHT] 外部角色同步未成功,按现有角色放行, username={}, result={}",
username, result);
return;
}
log.info("[JOINBRIGHT] 外部角色同步完成, username={}, msg={}",
username, result.get(AjaxResult.MSG_TAG));
} catch (Exception e) {
log.warn("[JOINBRIGHT] 外部角色同步异常,按现有角色放行, username={}", username, e);
}
}
private LoginUser loadPlatformUser(String username) {
try {
return sysLoginService.getLoginUserByUsername(username);
} catch (ServiceException e) {
String message = e.getMessage();
boolean accountState = message != null
&& (message.contains("停用") || message.contains("删除"));
throw new ServiceException(message,
accountState ? ExtAuthConstants.USER_DISABLED : ExtAuthConstants.SERVICE_UNAVAILABLE);
}
}
private int readCode(AjaxResult result) {
Object value = result.get(AjaxResult.CODE_TAG);
return value instanceof Number ? ((Number) value).intValue() : Response.FAIL;
}
private String defaultMessage(String message, String defaultMessage) {
return StringUtils.isNotEmpty(message) ? message : defaultMessage;
}
}

+ 28
- 2
inspect-admin/src/main/java/com/inspect/admin/service/SysLoginService.java View File

@ -4,6 +4,7 @@ import com.inspect.admin.SystemClient;
import com.inspect.base.core.domain.Response; import com.inspect.base.core.domain.Response;
import com.inspect.base.core.domain.SysWarning; import com.inspect.base.core.domain.SysWarning;
import com.inspect.base.core.enums.UserStatus; import com.inspect.base.core.enums.UserStatus;
import com.inspect.base.core.enums.UserType;
import com.inspect.base.core.exception.ServiceException; import com.inspect.base.core.exception.ServiceException;
import com.inspect.base.core.utils.JwtUtils; import com.inspect.base.core.utils.JwtUtils;
import com.inspect.base.core.utils.ServletUtils; import com.inspect.base.core.utils.ServletUtils;
@ -125,8 +126,8 @@ public class SysLoginService {
String username = userInfo.getUsername(); String username = userInfo.getUsername();
if(!roles.contains("admin")) { if(!roles.contains("admin")) {
String aollowIp = userInfo.getSysUser().getAollowIp(); String aollowIp = userInfo.getSysUser().getAollowIp();
if(StringUtils.isBlank(aollowIp)) {
throw new ServiceException("用户未绑定登录ip,不允许登录");
if (StringUtils.isBlank(aollowIp) || StringUtils.equals("*", aollowIp)) {
logger.info("允许用户任意ip登录");
} else if(!StringUtils.equals(requestIp, aollowIp)) { } else if(!StringUtils.equals(requestIp, aollowIp)) {
try { try {
SysWarning sysWarning = this.generateLoginErrorLogVo("用户登录ip与绑定ip不同,已禁止登录", Integer.valueOf(2), username); SysWarning sysWarning = this.generateLoginErrorLogVo("用户登录ip与绑定ip不同,已禁止登录", Integer.valueOf(2), username);
@ -244,4 +245,29 @@ public class SysLoginService {
this.remoteLogService.saveLogininfor(logininfor, "inner"); this.remoteLogService.saveLogininfor(logininfor, "inner");
} }
public LoginUser getLoginUserByUsername(String username) {
if (StringUtils.isAnyBlank(username)) {
throw new ServiceException("用户名不能为空");
}
Response<LoginUser> userResult = remoteUserService.getUserInfo(username, "inner");
if (Response.FAIL == userResult.getCode()
|| StringUtils.isNull(userResult)
|| StringUtils.isNull(userResult.getData())) {
throw new ServiceException("用户不存在或已禁用");
}
LoginUser userInfo = userResult.getData();
SysUser user = userInfo.getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
throw new ServiceException("账号已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
throw new ServiceException("账号已停用");
}
return userInfo;
}
} }

+ 16
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/ExtAuthConstants.java View File

@ -0,0 +1,16 @@
package com.inspect.base.core.constant;
/**
* 外部平台(joinbright)集成错误码。
* 由 dliip-auth 的 joinbrightLogin 统一返回给前端,便于前端区分“回登录页”与“服务异常”。
*/
public class ExtAuthConstants {
/** joinbright-token 缺失/无效,或外部平台未返回用户名 */
public static final int TOKEN_INVALID = 4001;
/** 平台账号已停用或已删除 */
public static final int USER_DISABLED = 4003;
/** 同名账号冲突(受保护账号不允许被外部账号绑定) */
public static final int USER_CONFLICT = 4009;
/** 外部认证 / 用户同步服务不可用 */
public static final int SERVICE_UNAVAILABLE = 5003;
}

+ 1
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/RedisConst.java View File

@ -13,4 +13,5 @@ public class RedisConst {
public static final String TASK_RECORD_CNT = "TASK_RECORD_CNT"; public static final String TASK_RECORD_CNT = "TASK_RECORD_CNT";
public static final String TASK_CURRENT_CODE = "TASK_CURRENT_CODE@"; public static final String TASK_CURRENT_CODE = "TASK_CURRENT_CODE@";
public static final String IMMEDIATELY_EXEC_TASK_TIME = "IMMEDIATELY_EXEC_TASK_TIME"; public static final String IMMEDIATELY_EXEC_TASK_TIME = "IMMEDIATELY_EXEC_TASK_TIME";
public static final String REPORT_PUSH_LOCK = "REPORT_PUSH_LOCK@";//任务完成自动归档+报告推送 防重锁
} }

+ 1
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/constant/TokenConst.java View File

@ -15,6 +15,7 @@ public class TokenConst {
public static final String LOGIN_TOKENS = "login_tokens:"; public static final String LOGIN_TOKENS = "login_tokens:";
public static final String DOWN_TOKEN = "downToken"; public static final String DOWN_TOKEN = "downToken";
public static final String ACCESS_TOKEN = "access_token"; public static final String ACCESS_TOKEN = "access_token";
public static final String TOKEN_TYPE = "token_type";
public static final String EXPIRES_IN = "expires_in"; public static final String EXPIRES_IN = "expires_in";
public static final String ADMIN_FLAG = "admin_flag"; public static final String ADMIN_FLAG = "admin_flag";
} }

inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgTypeEnum.java → inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AlgCategoryEnum.java View File


inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AnalysisTypeEnum.java → inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/AnalysisType.java View File


+ 21
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/DefectLevel.java View File

@ -0,0 +1,21 @@
package com.inspect.base.core.enums;
import lombok.Getter;
@Getter
public enum DefectLevel {
// 0=无, 1=紧急, 2=严重, 3=一般, 4=轻微
NONE(0, "无"),
URGENT(1, "重大"),
SERIOUS(2, "严重"),
NORMAL(3, "一般"),
MINOR(4, "轻微");
private final Integer code;
private final String info;
DefectLevel(Integer code, String info) {
this.code = code;
this.info = info;
}
}

+ 22
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/enums/UserType.java View File

@ -0,0 +1,22 @@
package com.inspect.base.core.enums;
public enum UserType {
SYSTEM("0", "系统用户"),
EXTERNAL("1", "外部用户");
private final String code;
private final String info;
private UserType(String code, String info) {
this.code = code;
this.info = info;
}
public String getCode() {
return this.code;
}
public String getInfo() {
return this.info;
}
}

+ 1
- 1
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/sftp/SftpClient.java View File

@ -113,7 +113,7 @@ public class SftpClient {
ftps.setFileType(FTPSClient.BINARY_FILE_TYPE); ftps.setFileType(FTPSClient.BINARY_FILE_TYPE);
sftpFactory.init(); sftpFactory.init();
} }
ftps.setControlEncoding("UTF-8");
ftps.enterLocalPassiveMode(); ftps.enterLocalPassiveMode();
ftps.enterLocalPassiveMode(); ftps.enterLocalPassiveMode();
ftps.setFileTransferMode(FTPSClient.STREAM_TRANSFER_MODE); ftps.setFileTransferMode(FTPSClient.STREAM_TRANSFER_MODE);


+ 110
- 3
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/utils/HttpClientUtils.java View File

@ -75,6 +75,31 @@ public class HttpClientUtils {
return connection.getInputStream(); return connection.getInputStream();
} }
public static InputStream getStream(String url) throws Exception {
URL realUrl = new URL(url);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
return connection.getInputStream();
}
public static InputStream postStream(String url, String jsonParam) throws Exception {
URL realUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "utf-8");
writer.write(jsonParam);
writer.flush();
writer.close();
return connection.getInputStream();
}
public static URLConnection getConnection(String url, String param) throws Exception { public static URLConnection getConnection(String url, String param) throws Exception {
String urlNameString = url + "?" + param; String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString); URL realUrl = new URL(urlNameString);
@ -126,6 +151,76 @@ public class HttpClientUtils {
return result.toString(); return result.toString();
} }
public static String get(String url) throws Exception {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try {
URL realUrl = new URL(url);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
throw e;
} finally {
if (null != in) {
in.close();
}
}
return result.toString();
}
public static String delete(String url) throws Exception {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
HttpURLConnection connection = null;
try {
URL realUrl = new URL(url);
connection = (HttpURLConnection) realUrl.openConnection();
connection.setRequestMethod("DELETE");
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
try {
if (connection != null && connection.getErrorStream() != null) {
BufferedReader errorReader = new BufferedReader(new InputStreamReader(connection.getErrorStream(), "UTF-8"));
String line;
while ((line = errorReader.readLine()) != null) {
result.append(line);
}
errorReader.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
} finally {
if (null != in) {
in.close();
}
if (null != connection) {
connection.disconnect();
}
}
return result.toString();
}
public static String postJson(String requestUrl, String requestPram, String tokenKey, String accessToken) { public static String postJson(String requestUrl, String requestPram, String tokenKey, String accessToken) {
OutputStreamWriter outputStreamWriter = null; OutputStreamWriter outputStreamWriter = null;
BufferedReader bufferedReader = null; BufferedReader bufferedReader = null;
@ -155,11 +250,23 @@ public class HttpClientUtils {
bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "utf-8")); bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "utf-8"));
String line; String line;
while ((line = bufferedReader.readLine()) != null) {
responseResult.append(line);
}
while ((line = bufferedReader.readLine()) != null) {
responseResult.append(line);
}
} catch (Exception var18) { } catch (Exception var18) {
var18.printStackTrace(); var18.printStackTrace();
try {
if (httpURLConnection != null && httpURLConnection.getErrorStream() != null) {
BufferedReader errorReader = new BufferedReader(new InputStreamReader(httpURLConnection.getErrorStream(), "utf-8"));
String line;
while ((line = errorReader.readLine()) != null) {
responseResult.append(line);
}
errorReader.close();
}
} catch (IOException var19) {
var19.printStackTrace();
}
} finally { } finally {
httpURLConnection.disconnect(); httpURLConnection.disconnect();


+ 4
- 0
inspect-base/inspect-base-core/src/main/java/com/inspect/base/core/web/domain/AjaxResult.java View File

@ -63,6 +63,10 @@ public class AjaxResult extends HashMap<String, Object> {
return new AjaxResult(code, msg, null); return new AjaxResult(code, msg, null);
} }
public static AjaxResult error(int code, String msg, Object data) {
return new AjaxResult(code, msg, data);
}
public static AjaxResult fail(Integer code, String msg, Object data) { public static AjaxResult fail(Integer code, String msg, Object data) {
return new AjaxResult(code, msg, data); return new AjaxResult(code, msg, data);
} }


+ 51
- 0
inspect-base/inspect-base-redis/src/main/java/com/inspect/base/redis/service/RedisService.java View File

@ -2,6 +2,7 @@ package com.inspect.base.redis.service;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -11,8 +12,11 @@ import com.inspect.base.core.constant.Color;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -135,6 +139,53 @@ public class RedisService {
return redisTemplate.opsForHash().multiGet(key, hKeys); return redisTemplate.opsForHash().multiGet(key, hKeys);
} }
/**
* 游标分批扫描 key(非阻塞)。
*
* <p>禁止在业务代码里使用 {@link #keys(String)}:KEYS 是 O(N) 的阻塞命令,而 Redis 是单线程的,
* 一次 KEYS 执行期间其它所有客户端的命令都要排队。keyspace 较大时单次 KEYS 需要十几毫秒,
* 若在高频路径上循环调用,会直接把 Redis 及其上游服务拖死(已在生产环境造成过故障)。
* 需要按 pattern 查 key 时一律使用本方法。
*
* @param pattern 匹配模式,例如 TASK_CODE@279@*
* @return 匹配到的 key 集合(SCAN 可能重复返回同一个 key,方法内部已去重)
*/
public Collection<String> scan(String pattern) {
return scan(pattern, 500);
}
/**
* @param count 每批返回的 key 数量提示值(COUNT,非硬性上限)
*/
public Collection<String> scan(String pattern, int count) {
Set<String> keys = new LinkedHashSet<>();
if (pattern == null || pattern.isEmpty()) {
return keys;
}
long start = System.currentTimeMillis();
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(count).build();
redisTemplate.execute((RedisCallback<Void>) connection -> {
try (Cursor<byte[]> cursor = connection.scan(options)) {
while (cursor.hasNext()) {
Object key = redisTemplate.getKeySerializer().deserialize(cursor.next());
if (key != null) {
keys.add(String.valueOf(key));
}
}
} catch (Exception e) {
logger.error("RedisService.scan error, pattern: {}", pattern, e);
}
return null;
});
logger.info("RedisService.scan pattern: {}, size: {}, cost: {} ms", pattern, keys.size(), System.currentTimeMillis() - start);
return keys;
}
/**
* @deprecated KEYS 会阻塞整个 Redis(单线程),生产环境禁止调用,请改用 {@link #scan(String)}。
*/
@Deprecated
public Collection<String> keys(String pattern) { public Collection<String> keys(String pattern) {
return redisTemplate.keys(pattern); return redisTemplate.keys(pattern);
} }


+ 10
- 0
inspect-base/inspect-base-system/src/main/java/com/inspect/system/base/domain/SysDept.java View File

@ -12,6 +12,8 @@ import org.apache.commons.lang3.builder.ToStringStyle;
public class SysDept extends BaseEntity { public class SysDept extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Long deptId; private Long deptId;
/** 外部平台部门ID,非空表示由同步任务管理 */
private String extId;
private Long parentId; private Long parentId;
private String ancestors; private String ancestors;
private String deptName; private String deptName;
@ -32,6 +34,14 @@ public class SysDept extends BaseEntity {
this.deptId = deptId; this.deptId = deptId;
} }
public String getExtId() {
return this.extId;
}
public void setExtId(String extId) {
this.extId = extId;
}
public Long getParentId() { public Long getParentId() {
return this.parentId; return this.parentId;
} }


+ 10
- 0
inspect-base/inspect-base-system/src/main/java/com/inspect/system/base/domain/SysRole.java View File

@ -15,6 +15,8 @@ public class SysRole extends BaseEntity {
cellType = ColumnType.NUMERIC cellType = ColumnType.NUMERIC
) )
private Long roleId; private Long roleId;
/** 外部平台角色ID,非空表示由同步任务管理 */
private String extId;
@Excel( @Excel(
name = "角色名称" name = "角色名称"
) )
@ -59,6 +61,14 @@ public class SysRole extends BaseEntity {
this.roleId = roleId; this.roleId = roleId;
} }
public String getExtId() {
return this.extId;
}
public void setExtId(String extId) {
this.extId = extId;
}
public boolean isAdmin() { public boolean isAdmin() {
return isAdmin(this.roleId); return isAdmin(this.roleId);
} }


+ 10
- 0
inspect-base/inspect-base-system/src/main/java/com/inspect/system/base/domain/SysUser.java View File

@ -28,6 +28,8 @@ public class SysUser extends BaseEntity {
prompt = "用户编号" prompt = "用户编号"
) )
private Long userId; private Long userId;
/** 外部平台用户ID,非空表示由同步任务管理 */
private String extId;
@Excel( @Excel(
name = "部门编号", name = "部门编号",
type = Type.IMPORT type = Type.IMPORT
@ -90,6 +92,7 @@ public class SysUser extends BaseEntity {
private Long roleId; private Long roleId;
private Date passUpdateTime; private Date passUpdateTime;
private String aollowIp; private String aollowIp;
private String userType = "0";
public String getAollowIp() { public String getAollowIp() {
return this.aollowIp; return this.aollowIp;
@ -106,6 +109,10 @@ public class SysUser extends BaseEntity {
this.userId = userId; this.userId = userId;
} }
public String getExtId() {
return extId;
}
public Long getUserId() { public Long getUserId() {
return this.userId; return this.userId;
} }
@ -219,6 +226,8 @@ public class SysUser extends BaseEntity {
return this.roleId; return this.roleId;
} }
public String getUserType() { return this.userType; }
@Override @Override
public String toString() { public String toString() {
return "SysUser{" + return "SysUser{" +
@ -241,6 +250,7 @@ public class SysUser extends BaseEntity {
", postIds=" + Arrays.toString(postIds) + ", postIds=" + Arrays.toString(postIds) +
", roleId=" + roleId + ", roleId=" + roleId +
", passUpdateTime=" + passUpdateTime + ", passUpdateTime=" + passUpdateTime +
", userType=" + userType +
", aollowIp='" + aollowIp + '\'' + ", aollowIp='" + aollowIp + '\'' +
'}'; '}';
} }


+ 4
- 3
inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/aspect/PreAuthorizeAspect.java View File

@ -47,10 +47,11 @@ public class PreAuthorizeAspect {
AuthUtil.checkRole(requiresRoles); AuthUtil.checkRole(requiresRoles);
} }
// TEMP 临时屏蔽 RequiresPermissions 注解
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class); RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
if (requiresPermissions != null) {
AuthUtil.checkPermission(requiresPermissions);
}
// if (requiresPermissions != null) {
// AuthUtil.checkPermission(requiresPermissions);
// }
} }
} }

+ 1
- 2
inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/handler/GlobalExceptionHandler.java View File

@ -13,13 +13,12 @@ import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
//@RestControllerAdvice
@RestControllerAdvice
public class GlobalExceptionHandler { public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);


+ 1
- 0
inspect-common/inspect-common-security/src/main/java/com/inspect/common/security/service/TokenService.java View File

@ -53,6 +53,7 @@ public class TokenService {
Map<String, Object> rspMap = new HashMap<>(); Map<String, Object> rspMap = new HashMap<>();
rspMap.put(TokenConst.ACCESS_TOKEN, JwtUtils.createToken(claimsMap)); rspMap.put(TokenConst.ACCESS_TOKEN, JwtUtils.createToken(claimsMap));
rspMap.put(TokenConst.EXPIRES_IN, expireTime); rspMap.put(TokenConst.EXPIRES_IN, expireTime);
rspMap.put(TokenConst.TOKEN_TYPE, "Bearer");
return rspMap; return rspMap;
} }


+ 15
- 0
inspect-ivs/src/main/java/com/inspect/ivs/constant/IvsConst.java View File

@ -6,6 +6,7 @@ public class IvsConst {
public static final String URI_MODIFY_PASSWORD = "/users/userPassword/v1.0"; public static final String URI_MODIFY_PASSWORD = "/users/userPassword/v1.0";
public static final String URI_KEEP_ALIVE = "/common/keepAlive"; public static final String URI_KEEP_ALIVE = "/common/keepAlive";
public static final String URI_PTZ_CONTROL = "/device/ptzcontrol"; public static final String URI_PTZ_CONTROL = "/device/ptzcontrol";
public static final String URI_POSITION_3D = "/device/position3d";
public static final String URI_DEVICE_LIST = "/device/deviceList/v1.0"; public static final String URI_DEVICE_LIST = "/device/deviceList/v1.0";
public static final String URI_PRESET_SET = "/ptz/presetposition/v1.0"; public static final String URI_PRESET_SET = "/ptz/presetposition/v1.0";
public static final String URI_START_REAL_PLAY = "/device/startrealplaybyipex"; public static final String URI_START_REAL_PLAY = "/device/startrealplaybyipex";
@ -21,6 +22,20 @@ public class IvsConst {
public static final String URI_CONTROL_RESUME_RECORD = "/playback/resume/v1.0/{playHandle}"; public static final String URI_CONTROL_RESUME_RECORD = "/playback/resume/v1.0/{playHandle}";
public static final String URI_PLAYBACK_START = "/playback/startplatformplaybackbyip"; public static final String URI_PLAYBACK_START = "/playback/startplatformplaybackbyip";
public static final String URI_PLAYBACK_STOP = "/playback/stopplatformplaybackbyip/{handle}"; public static final String URI_PLAYBACK_STOP = "/playback/stopplatformplaybackbyip/{handle}";
public static final String URI_HLS_SESSIONS = "/hik/nvr/playback/hls/sessions";
public static final String URI_HLS_SESSION = "/hik/nvr/playback/hls/sessions/%s";
public static final String URI_HLS_SESSION_CONTROL = "/hik/nvr/playback/hls/sessions/%s/controls";
public static final String URI_HLS_UI_SESSIONS = "/hik/nvr/playback/hls/ui/sessions";
public static final String URI_HLS_UI_SESSION_CONTROL = "/hik/nvr/playback/hls/ui/sessions/%s/controls";
public static final String URI_UI_CONFIG = "/hik/nvr/playback/hls/ui/config";
public static final String URI_LIVE_START = "/hik/nvr/playback/hls/ui/actions/live/start";
public static final String URI_LIVE_STOP = "/hik/nvr/playback/hls/ui/actions/live/stop";
public static final String URI_LIVE_STATUS = "/hik/nvr/playback/hls/ui/actions/live/status";
public static final String URI_PRESET_LIST_PAGE = "/device/ptzpresetlist/%s/%s";
public static final String URI_PRESET_CREATE = "/hik/nvr/playback/hls/ui/actions/preset/create";
public static final String URI_SNAPSHOT_TASK = "/platform/platformSnapshot/%s/%s";
public static final String URI_SNAPSHOT_DOWNLOAD = "/platform/snapshot/%s";
public static final String URI_CLIP_DOWNLOAD = "/hik/nvr/playback/clips/download";
public static final String URI_PRESET_REMOVE = "/ptz/presetposition/{cameraCode}/{domainCode}/{presetIndex}/v1.0"; public static final String URI_PRESET_REMOVE = "/ptz/presetposition/{cameraCode}/{domainCode}/{presetIndex}/v1.0";
public static final String URI_PRESET_LIST = "/device/ptzpresetlist/{cameraCode}/{domainCode}"; public static final String URI_PRESET_LIST = "/device/ptzpresetlist/{cameraCode}/{domainCode}";
public static final String IVS_TOKEN = "IVS_TOKEN"; public static final String IVS_TOKEN = "IVS_TOKEN";


+ 33
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsControlController.java View File

@ -6,6 +6,7 @@ import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.redis.service.RedisService; import com.inspect.base.redis.service.RedisService;
import com.inspect.ivs.base.feign.domain.IvsResult; import com.inspect.ivs.base.feign.domain.IvsResult;
import com.inspect.ivs.constant.IvsConst; import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService; import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.IvsResourceRetryableDelegate; import com.inspect.ivs.service.IvsResourceRetryableDelegate;
import com.inspect.ivs.util.UriUtils; import com.inspect.ivs.util.UriUtils;
@ -19,8 +20,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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 org.springframework.http.ResponseEntity;
import java.io.IOException; import java.io.IOException;
@ -32,15 +37,18 @@ public class IvsControlController {
private final RedisService redisService; private final RedisService redisService;
private final IvsCommonService ivsCommonService; private final IvsCommonService ivsCommonService;
private final IvsResourceRetryableDelegate ivsResourceRetryableDelegate; private final IvsResourceRetryableDelegate ivsResourceRetryableDelegate;
private final HlsPlaybackComponent hlsPlaybackComponent;
@Value("${ivs.version:3800}") @Value("${ivs.version:3800}")
private String version; private String version;
public IvsControlController( public IvsControlController(
RedisService redisService, RedisService redisService,
IvsCommonService ivsCommonService, IvsResourceRetryableDelegate ivsResourceRetryableDelegate) {
IvsCommonService ivsCommonService, IvsResourceRetryableDelegate ivsResourceRetryableDelegate,
HlsPlaybackComponent hlsPlaybackComponent) {
this.redisService = redisService; this.redisService = redisService;
this.ivsCommonService = ivsCommonService; this.ivsCommonService = ivsCommonService;
this.ivsResourceRetryableDelegate = ivsResourceRetryableDelegate; this.ivsResourceRetryableDelegate = ivsResourceRetryableDelegate;
this.hlsPlaybackComponent = hlsPlaybackComponent;
} }
@GetMapping({"ptz"}) @GetMapping({"ptz"})
@ -155,4 +163,28 @@ public class IvsControlController {
log.debug("[IVS] END"); log.debug("[IVS] END");
return ivsPresetListView; return ivsPresetListView;
} }
@PostMapping({"ptzControl"})
public ResponseEntity<JSONObject> ptzControl(@RequestBody JSONObject request) {
log.debug("[IVS] ptz control page param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_PTZ_CONTROL, request);
}
@PostMapping({"position3d"})
public ResponseEntity<JSONObject> position3d(@RequestBody JSONObject request) {
log.debug("[IVS] position3d page param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_POSITION_3D, request);
}
@GetMapping({"presetList/{deviceCode}/{domainCode}"})
public ResponseEntity<JSONObject> presetListPage(@PathVariable String deviceCode, @PathVariable String domainCode) {
log.debug("[IVS] preset list page deviceCode:{}, domainCode:{}", deviceCode, domainCode);
return this.hlsPlaybackComponent.get(String.format(IvsConst.URI_PRESET_LIST_PAGE, deviceCode, domainCode));
}
@PostMapping({"presetCreate"})
public ResponseEntity<JSONObject> presetCreate(@RequestBody JSONObject request) {
log.debug("[IVS] preset create param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_PRESET_CREATE, request);
}
} }

+ 22
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsDeviceController.java View File

@ -9,6 +9,7 @@ import com.inspect.ivs.base.feign.view.SipbDeviceListView;
import com.inspect.ivs.constant.IvsConst; import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.domain.Temp; import com.inspect.ivs.domain.Temp;
import com.inspect.ivs.domain.TempConfiguration; import com.inspect.ivs.domain.TempConfiguration;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService; import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.IvsResourceRetryableDelegate; import com.inspect.ivs.service.IvsResourceRetryableDelegate;
import com.inspect.ivs.util.UriUtils; import com.inspect.ivs.util.UriUtils;
@ -47,12 +48,14 @@ public class IvsDeviceController {
private boolean testMode; private boolean testMode;
private final IvsCommonService ivsCommonService; private final IvsCommonService ivsCommonService;
private final HlsPlaybackComponent hlsPlaybackComponent;
@Resource @Resource
private IvsResourceRetryableDelegate ivsResourceRetryableDelegate; private IvsResourceRetryableDelegate ivsResourceRetryableDelegate;
public IvsDeviceController(IvsCommonService ivsCommonService) {
public IvsDeviceController(IvsCommonService ivsCommonService, HlsPlaybackComponent hlsPlaybackComponent) {
this.ivsCommonService = ivsCommonService; this.ivsCommonService = ivsCommonService;
this.hlsPlaybackComponent = hlsPlaybackComponent;
} }
@ -471,6 +474,24 @@ public class IvsDeviceController {
return ivsCommonService.getResultJson(ivsResourceRetryableDelegate.getLabel(), UriUtils.parse(IvsConst.URI_DEVICE_LIST, ivsDevChanListVo)); return ivsCommonService.getResultJson(ivsResourceRetryableDelegate.getLabel(), UriUtils.parse(IvsConst.URI_DEVICE_LIST, ivsDevChanListVo));
} }
@GetMapping({"hls/config"})
public ResponseEntity<JSONObject> hlsConfig() {
log.debug("[DEVICE] hls config");
return this.hlsPlaybackComponent.get(IvsConst.URI_UI_CONFIG);
}
@GetMapping({"platformSnapshot/{deviceCode}/{domainCode}"})
public ResponseEntity<JSONObject> createSnapshotTask(@PathVariable String deviceCode, @PathVariable String domainCode) {
log.debug("[DEVICE] create snapshot task deviceCode:{}, domainCode:{}", deviceCode, domainCode);
return this.hlsPlaybackComponent.get(String.format(IvsConst.URI_SNAPSHOT_TASK, deviceCode, domainCode));
}
@GetMapping({"snapshot/{taskId}"})
public ResponseEntity<byte[]> downloadSnapshot(@PathVariable String taskId) {
log.debug("[DEVICE] download snapshot taskId:{}", taskId);
return this.hlsPlaybackComponent.getBinary(String.format(IvsConst.URI_SNAPSHOT_DOWNLOAD, taskId));
}
private static int calculateSum(int n) { private static int calculateSum(int n) {
int sum = 0; int sum = 0;
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {


+ 45
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsPlaybackController.java View File

@ -7,6 +7,7 @@ import com.inspect.base.redis.service.RedisService;
import com.inspect.ivs.constant.IvsConst; import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.domain.CameraCodeAndEndTime; import com.inspect.ivs.domain.CameraCodeAndEndTime;
import com.inspect.ivs.enums.RtspType; import com.inspect.ivs.enums.RtspType;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService; import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.PlaybackComponent; import com.inspect.ivs.service.PlaybackComponent;
import com.inspect.ivs.service.XinTongComponent; import com.inspect.ivs.service.XinTongComponent;
@ -32,7 +33,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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 org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
@ -45,15 +51,17 @@ public class IvsPlaybackController {
private final ZlMediaComponent zlmediaComponent; private final ZlMediaComponent zlmediaComponent;
private final RedisService redisService; private final RedisService redisService;
private final PlaybackComponent playbackComponent; private final PlaybackComponent playbackComponent;
private final HlsPlaybackComponent hlsPlaybackComponent;
@Value("${rtsp.type:xintong}") @Value("${rtsp.type:xintong}")
private RtspType rtspType; private RtspType rtspType;
private final XinTongComponent xinTongComponent; private final XinTongComponent xinTongComponent;
public IvsPlaybackController(IvsCommonService ivsCommonService, ZlMediaComponent zlmediaComponent, RedisService redisService, PlaybackComponent playbackComponent, XinTongComponent xinTongComponent) {
public IvsPlaybackController(IvsCommonService ivsCommonService, ZlMediaComponent zlmediaComponent, RedisService redisService, PlaybackComponent playbackComponent, HlsPlaybackComponent hlsPlaybackComponent, XinTongComponent xinTongComponent) {
this.ivsCommonService = ivsCommonService; this.ivsCommonService = ivsCommonService;
this.zlmediaComponent = zlmediaComponent; this.zlmediaComponent = zlmediaComponent;
this.redisService = redisService; this.redisService = redisService;
this.playbackComponent = playbackComponent; this.playbackComponent = playbackComponent;
this.hlsPlaybackComponent = hlsPlaybackComponent;
this.xinTongComponent = xinTongComponent; this.xinTongComponent = xinTongComponent;
} }
@ -116,6 +124,42 @@ public class IvsPlaybackController {
return Response.ok(); return Response.ok();
} }
@PostMapping({"hls/sessions"})
public ResponseEntity<JSONObject> hlsCreateSession(@RequestBody JSONObject request) {
log.debug("[PLAYBACK] hls create session param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_HLS_SESSIONS, request);
}
@DeleteMapping({"hls/sessions/{sessionId}"})
public ResponseEntity<JSONObject> hlsDeleteSession(@PathVariable String sessionId) {
log.debug("[PLAYBACK] hls delete session sessionId:{}", sessionId);
return this.hlsPlaybackComponent.delete(String.format(IvsConst.URI_HLS_SESSION, sessionId));
}
@GetMapping({"hls/sessions/{sessionId}"})
public ResponseEntity<JSONObject> hlsGetSession(@PathVariable String sessionId) {
log.debug("[PLAYBACK] hls query session sessionId:{}", sessionId);
return this.hlsPlaybackComponent.get(String.format(IvsConst.URI_HLS_SESSION, sessionId));
}
@PostMapping({"hls/sessions/{sessionId}/controls"})
public ResponseEntity<JSONObject> hlsControlSession(@PathVariable String sessionId, @RequestBody JSONObject request) {
log.debug("[PLAYBACK] hls control session sessionId:{}, param:{}", sessionId, request);
return this.hlsPlaybackComponent.post(String.format(IvsConst.URI_HLS_SESSION_CONTROL, sessionId), request);
}
@PostMapping({"hls/ui/sessions"})
public ResponseEntity<JSONObject> hlsCreateUiSession(@RequestBody JSONObject request) {
log.debug("[PLAYBACK] hls ui create session param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_HLS_UI_SESSIONS, request);
}
@PostMapping({"hls/ui/sessions/{sessionId}/controls"})
public ResponseEntity<JSONObject> hlsControlUiSession(@PathVariable String sessionId, @RequestBody JSONObject request) {
log.debug("[PLAYBACK] hls ui control session sessionId:{}, param:{}", sessionId, request);
return this.hlsPlaybackComponent.post(String.format(IvsConst.URI_HLS_UI_SESSION_CONTROL, sessionId), request);
}
public String getLabel() { public String getLabel() {
return UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY); return UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY);
} }


+ 14
- 1
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsRecordController.java View File

@ -1,10 +1,12 @@
package com.inspect.ivs.controller; package com.inspect.ivs.controller;
import com.alibaba.fastjson.JSONObject;
import com.inspect.base.core.domain.Response; import com.inspect.base.core.domain.Response;
import com.inspect.base.core.utils.DateUtils; import com.inspect.base.core.utils.DateUtils;
import com.inspect.base.core.utils.StringUtils; import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.redis.service.RedisService; import com.inspect.base.redis.service.RedisService;
import com.inspect.ivs.constant.IvsConst; import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService; import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.ZlMediaComponent; import com.inspect.ivs.service.ZlMediaComponent;
import com.inspect.ivs.util.UriUtils; import com.inspect.ivs.util.UriUtils;
@ -22,7 +24,10 @@ import java.util.concurrent.TimeUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -33,11 +38,13 @@ public class IvsRecordController {
private final IvsCommonService ivsCommonService; private final IvsCommonService ivsCommonService;
private final RedisService redisService; private final RedisService redisService;
private final ZlMediaComponent zlmediaComponent; private final ZlMediaComponent zlmediaComponent;
private final HlsPlaybackComponent hlsPlaybackComponent;
public IvsRecordController(IvsCommonService ivsCommonService, RedisService redisService, ZlMediaComponent zlmediaComponent) {
public IvsRecordController(IvsCommonService ivsCommonService, RedisService redisService, ZlMediaComponent zlmediaComponent, HlsPlaybackComponent hlsPlaybackComponent) {
this.ivsCommonService = ivsCommonService; this.ivsCommonService = ivsCommonService;
this.redisService = redisService; this.redisService = redisService;
this.zlmediaComponent = zlmediaComponent; this.zlmediaComponent = zlmediaComponent;
this.hlsPlaybackComponent = hlsPlaybackComponent;
} }
@GetMapping({"start"}) @GetMapping({"start"})
@ -83,6 +90,12 @@ public class IvsRecordController {
return ivsRecStopView; return ivsRecStopView;
} }
@PostMapping({"clipDownload"})
public ResponseEntity<byte[]> clipDownload(@RequestBody JSONObject request) {
log.debug("[RECORD] clip download param:{}", request);
return this.hlsPlaybackComponent.postBinary(IvsConst.URI_CLIP_DOWNLOAD, request);
}
public String getLabel() { public String getLabel() {
return UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY); return UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY);
} }


+ 30
- 3
inspect-ivs/src/main/java/com/inspect/ivs/controller/IvsStreamController.java View File

@ -1,11 +1,13 @@
package com.inspect.ivs.controller; package com.inspect.ivs.controller;
import com.alibaba.fastjson.JSONObject;
import com.inspect.base.core.domain.Response; import com.inspect.base.core.domain.Response;
import com.inspect.base.core.utils.StringUtils; import com.inspect.base.core.utils.StringUtils;
import com.inspect.base.core.utils.uuid.UUID; import com.inspect.base.core.utils.uuid.UUID;
import com.inspect.base.redis.service.RedisService; import com.inspect.base.redis.service.RedisService;
import com.inspect.ivs.constant.IvsConst; import com.inspect.ivs.constant.IvsConst;
import com.inspect.ivs.enums.RtspType; import com.inspect.ivs.enums.RtspType;
import com.inspect.ivs.service.HlsPlaybackComponent;
import com.inspect.ivs.service.IvsCommonService; import com.inspect.ivs.service.IvsCommonService;
import com.inspect.ivs.service.XinTongComponent; import com.inspect.ivs.service.XinTongComponent;
import com.inspect.ivs.service.ZlMediaComponent; import com.inspect.ivs.service.ZlMediaComponent;
@ -16,12 +18,16 @@ import com.inspect.ivs.vo.IvsVideoRtspVo;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.net.URLEncoder;
@RestController @RestController
@RequestMapping({"/api/v1/stream"}) @RequestMapping({"/api/v1/stream"})
@ -30,6 +36,7 @@ public class IvsStreamController {
private final IvsCommonService ivsCommonService; private final IvsCommonService ivsCommonService;
private final ZlMediaComponent zlMediaComponent; private final ZlMediaComponent zlMediaComponent;
private final XinTongComponent xinTongComponent; private final XinTongComponent xinTongComponent;
private final HlsPlaybackComponent hlsPlaybackComponent;
@Value("${ivs.test:false}") @Value("${ivs.test:false}")
private Boolean bIvsTestMode; private Boolean bIvsTestMode;
@Value("${rtsp.type:xintong}") @Value("${rtsp.type:xintong}")
@ -37,10 +44,11 @@ public class IvsStreamController {
@Resource @Resource
private RedisService redisService; private RedisService redisService;
public IvsStreamController(IvsCommonService ivsCommonService, ZlMediaComponent zlMediaComponent, XinTongComponent xinTongComponent) {
public IvsStreamController(IvsCommonService ivsCommonService, ZlMediaComponent zlMediaComponent, XinTongComponent xinTongComponent, HlsPlaybackComponent hlsPlaybackComponent) {
this.ivsCommonService = ivsCommonService; this.ivsCommonService = ivsCommonService;
this.zlMediaComponent = zlMediaComponent; this.zlMediaComponent = zlMediaComponent;
this.xinTongComponent = xinTongComponent; this.xinTongComponent = xinTongComponent;
this.hlsPlaybackComponent = hlsPlaybackComponent;
} }
@GetMapping({"start"}) @GetMapping({"start"})
@ -55,7 +63,7 @@ public class IvsStreamController {
// .rtspURL("rtsp://admin:wd19216811@192.168.1.249:554/h264/ch39/main/av_stream") // .rtspURL("rtsp://admin:wd19216811@192.168.1.249:554/h264/ch39/main/av_stream")
// .build(); // .build();
ivsVideoRtspView = new IvsVideoRtspView(); ivsVideoRtspView = new IvsVideoRtspView();
ivsVideoRtspView.setRtspURL("rtsp://admin:htjc2018@192.168.1.134:554/cam/realmonitor?channel=3&subtype=0");
ivsVideoRtspView.setRtspURL("rtsp://admin:htjc2018@192.168.1.64:554/Streaming/Channels/102");
} else { } else {
String rtspUrl = (String) this.redisService.redisTemplate.opsForValue().get("sys_config:RTSP_" + ivsStreamStartVo.getCameraCode()); String rtspUrl = (String) this.redisService.redisTemplate.opsForValue().get("sys_config:RTSP_" + ivsStreamStartVo.getCameraCode());
if (StringUtils.isNotEmpty(rtspUrl)) { if (StringUtils.isNotEmpty(rtspUrl)) {
@ -108,7 +116,7 @@ public class IvsStreamController {
// .rtspURL("rtsp://admin:wd19216811@192.168.1.249:554/h264/ch39/main/av_stream") // .rtspURL("rtsp://admin:wd19216811@192.168.1.249:554/h264/ch39/main/av_stream")
// .build(); // .build();
ivsVideoRtspView = new IvsVideoRtspView(); ivsVideoRtspView = new IvsVideoRtspView();
ivsVideoRtspView.setRtspURL("rtsp://admin:htjc2018@192.168.1.134:554/cam/realmonitor?channel=3&subtype=0");
ivsVideoRtspView.setRtspURL("rtsp://admin:htjc2018@192.168.1.64:554/Streaming/Channels/102");
} else { } else {
String label = java.util.UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY); String label = java.util.UUID.randomUUID().toString().trim().replaceAll(StringUtils.DASH, StringUtils.EMPTY);
ivsVideoRtspView = ivsCommonService.postJson( ivsVideoRtspView = ivsCommonService.postJson(
@ -122,4 +130,23 @@ public class IvsStreamController {
return ivsVideoRtspView; return ivsVideoRtspView;
} }
@PostMapping({"live/start"})
public ResponseEntity<JSONObject> liveStart(@RequestBody JSONObject request) {
log.debug("[STREAM] live start param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_LIVE_START, request);
}
@PostMapping({"live/stop"})
public ResponseEntity<JSONObject> liveStop(@RequestBody JSONObject request) {
log.debug("[STREAM] live stop param:{}", request);
return this.hlsPlaybackComponent.post(IvsConst.URI_LIVE_STOP, request);
}
@GetMapping({"live/status"})
public ResponseEntity<JSONObject> liveStatus(@RequestParam("cameraCode") String cameraCode) throws Exception {
log.debug("[STREAM] live status cameraCode:{}", cameraCode);
String uri = IvsConst.URI_LIVE_STATUS + "?cameraCode=" + URLEncoder.encode(cameraCode, "UTF-8");
return this.hlsPlaybackComponent.get(uri);
}
} }

+ 102
- 0
inspect-ivs/src/main/java/com/inspect/ivs/service/HlsPlaybackComponent.java View File

@ -0,0 +1,102 @@
package com.inspect.ivs.service;
import com.alibaba.fastjson.JSONObject;
import com.inspect.ivs.util.WebClientUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientException;
@Component
public class HlsPlaybackComponent {
private static final Logger log = LoggerFactory.getLogger(HlsPlaybackComponent.class);
@Value("${ivs.nvr.hls.url:http://192.168.1.86:28081}")
private String url;
private final WebClient webClient = WebClientUtils.create();
public ResponseEntity<JSONObject> get(String uri) {
log.info("[NVR HLS] GET url: {}", url.concat(uri));
return exchange(webClient.get().uri(url.concat(uri)));
}
public ResponseEntity<JSONObject> post(String uri, Object body) {
log.info("[NVR HLS] POST url: {}, body: {}", url.concat(uri), JSONObject.toJSONString(body));
return exchange(webClient.post().uri(url.concat(uri))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body));
}
public ResponseEntity<JSONObject> delete(String uri) {
log.info("[NVR HLS] DELETE url: {}", url.concat(uri));
return exchange(webClient.delete().uri(url.concat(uri)));
}
public ResponseEntity<byte[]> getBinary(String uri) {
log.info("[NVR HLS] GET binary url: {}", url.concat(uri));
return exchangeBinary(webClient.get().uri(url.concat(uri)));
}
public ResponseEntity<byte[]> postBinary(String uri, Object body) {
log.info("[NVR HLS] POST binary url: {}, body: {}", url.concat(uri), JSONObject.toJSONString(body));
return exchangeBinary(webClient.post().uri(url.concat(uri))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body));
}
private ResponseEntity<JSONObject> exchange(WebClient.RequestHeadersSpec<?> spec) {
assert webClient != null;
try {
return spec.exchangeToMono(response ->
response.bodyToMono(String.class)
.defaultIfEmpty("")
.map(body -> {
int status = response.statusCode().value();
ResponseEntity<JSONObject> entity;
if (body == null || body.trim().isEmpty()) {
entity = new ResponseEntity<>((JSONObject) null, HttpStatus.valueOf(status));
} else {
entity = ResponseEntity.status(status)
.contentType(MediaType.APPLICATION_JSON)
.body(JSONObject.parseObject(body));
}
return entity;
}))
.block();
} catch (WebClientException e) {
log.error("[NVR HLS] request failed, url: {}", url, e);
JSONObject error = new JSONObject();
error.put("code", HttpStatus.BAD_GATEWAY.value());
error.put("message", "NVR HLS服务不可用");
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(error);
}
}
private ResponseEntity<byte[]> exchangeBinary(WebClient.RequestHeadersSpec<?> spec) {
assert webClient != null;
try {
return spec.exchangeToMono(response ->
response.bodyToMono(byte[].class)
.defaultIfEmpty(new byte[0])
.map(bytes -> {
MediaType mediaType = response.headers().contentType().orElse(MediaType.APPLICATION_OCTET_STREAM);
return ResponseEntity.status(response.statusCode().value())
.contentType(mediaType)
.body(bytes);
}))
.block();
} catch (WebClientException e) {
log.error("[NVR HLS] request failed, url: {}", url, e);
return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
.contentType(MediaType.APPLICATION_JSON)
.body(new byte[0]);
}
}
}

+ 3
- 0
inspect-ivs/src/main/resources/bootstrap.yml View File

@ -25,3 +25,6 @@ ivs:
password: Huawei@1234 password: Huawei@1234
newPassword: Huawei@12345 newPassword: Huawei@12345
address: http://172.21.101.227:18530 address: http://172.21.101.227:18530
nvr:
hls:
url: http://192.168.1.86:28081

+ 23
- 0
inspect-job/src/main/java/com/inspect/job/client/ExtOrgSyncClient.java View File

@ -0,0 +1,23 @@
package com.inspect.job.client;
import com.inspect.base.core.web.domain.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
/**
* 外部平台组织同步客户端(调用 inspect-management 的内部接口)。
* 定时任务线程没有请求上下文,必须显式传 from-source=inner。
*/
@FeignClient(name = "inspect-management", contextId = "ext-org-sync")
public interface ExtOrgSyncClient {
@PostMapping("/inner/ext_sync/all")
AjaxResult syncAll(@RequestHeader("from-source") String source);
@PostMapping("/inner/ext_sync/dept")
AjaxResult syncDept(@RequestHeader("from-source") String source);
@PostMapping("/inner/ext_sync/user")
AjaxResult syncUser(@RequestHeader("from-source") String source);
}

+ 26
- 0
inspect-job/src/main/java/com/inspect/job/client/ReportArchiveClient.java View File

@ -0,0 +1,26 @@
package com.inspect.job.client;
import com.inspect.base.core.web.domain.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 巡检报告定时归档客户端(调用 inspect-main 的动环归档按时间窗口接口)。
*
* @author inspect
*/
@FeignClient(name = "inspect-main", contextId = "inspect-report-archive")
public interface ReportArchiveClient {
/**
* 归档 [startTime, endTime) 窗口内“已完成且未归档”的巡检执行:
* 合并生成一份动环报告,报告生成上传后自动推送外部平台;无待归档任务时不生成不推送。
*
* @param startTime 窗口开始时间 yyyy-MM-dd HH:mm:ss(含)
* @param endTime 窗口结束时间 yyyy-MM-dd HH:mm:ss(不含)
*/
@PostMapping({"/resultmain/archiveByTime"})
AjaxResult archiveByTime(@RequestParam("startTime") String startTime,
@RequestParam("endTime") String endTime);
}

+ 63
- 0
inspect-job/src/main/java/com/inspect/job/task/ExtOrgSyncTask.java View File

@ -0,0 +1,63 @@
package com.inspect.job.task;
import com.inspect.base.core.constant.SecurityConstants;
import com.inspect.base.core.web.domain.AjaxResult;
import com.inspect.job.client.ExtOrgSyncClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.function.Function;
/**
* 外部平台(盐源智慧感知平台)部门 / 用户同步定时任务。
* <p>
* 在“定时任务”中配置调用:
* <ul>
* <li>{@code extOrgSyncTask.syncAll()}:部门 + 用户全量同步(建议每日凌晨);</li>
* <li>{@code extOrgSyncTask.syncUser()}:部门 + 用户同步;</li>
* <li>{@code extOrgSyncTask.syncDept()}:仅部门同步。</li>
* </ul>
* 角色同步暂缓。调用失败直接抛出异常,由 sys_job_log 记录失败原因。
*/
@Slf4j
@Component("extOrgSyncTask")
public class ExtOrgSyncTask {
private final ExtOrgSyncClient extOrgSyncClient;
public ExtOrgSyncTask(ExtOrgSyncClient extOrgSyncClient) {
this.extOrgSyncClient = extOrgSyncClient;
}
public void syncAll() {
invoke("外部组织全量同步", extOrgSyncClient::syncAll);
}
public void syncDept() {
invoke("外部部门同步", extOrgSyncClient::syncDept);
}
public void syncUser() {
invoke("外部用户同步", extOrgSyncClient::syncUser);
}
private void invoke(String action, Function<String, AjaxResult> call) {
long start = System.currentTimeMillis();
log.info("[EXT-SYNC] {} 开始", action);
AjaxResult result;
try {
result = call.apply(SecurityConstants.INNER);
} catch (Exception e) {
log.error("[EXT-SYNC] {} 调用异常", action, e);
throw new RuntimeException(action + "调用异常: " + e.getMessage(), e);
}
Object code = result == null ? null : result.get(AjaxResult.CODE_TAG);
Object msg = result == null ? null : result.get(AjaxResult.MSG_TAG);
long elapsed = System.currentTimeMillis() - start;
if (result == null || !(code instanceof Number) || ((Number) code).intValue() != 200) {
log.error("[EXT-SYNC] {} 失败, code={}, msg={}, 耗时={}ms", action, code, msg, elapsed);
throw new RuntimeException(action + "失败: " + (msg == null ? "无响应" : msg));
}
log.info("[EXT-SYNC] {} 完成, msg={}, 耗时={}ms", action, msg, elapsed);
}
}

+ 15
- 15
inspect-job/src/main/java/com/inspect/job/task/JobMainTask.java View File

@ -35,6 +35,7 @@ import java.net.URLConnection;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
@ -1173,16 +1174,11 @@ public class JobMainTask {
.isEnable("1") .isEnable("1")
.build(); .build();
List<PatrolPresetPos> presetPosList = taskExecClient.selectPatrolPresetPosList(patrolPresetPos); List<PatrolPresetPos> presetPosList = taskExecClient.selectPatrolPresetPosList(patrolPresetPos);
PatrolPresetPos presetPos = !presetPosList.isEmpty() ? presetPosList.get(0) :
PatrolPresetPos.builder().presetPosId("1")
.presetPosCode("1")
.channelCode("ffffffffffffffffff#ffffffffffffffffffffffffffffffff")
.channelId("999999")
.patrolPointId(1234567890L)
.channelType("vl")
.isEnable("1")
.eqpBookId("1")
.build();
if (presetPosList.isEmpty()) {
log.info("getInfosByRecord: {} PatrolPresetPos no found", patrolTaskInfo.getDeviceId());
continue;
}
PatrolPresetPos presetPos = presetPosList.get(0);
String deviceCode = "ffffffffffffffffff"; String deviceCode = "ffffffffffffffffff";
try { try {
String[] channelCodeList = presetPos.getChannelCode().split(StringUtils.HASHTAG); String[] channelCodeList = presetPos.getChannelCode().split(StringUtils.HASHTAG);
@ -2039,12 +2035,14 @@ public class JobMainTask {
List<PatrolTask> patrolTaskControlList = new ArrayList<>(); List<PatrolTask> patrolTaskControlList = new ArrayList<>();
for (PatrolTask patrolTask : patrolTaskList) { for (PatrolTask patrolTask : patrolTaskList) {
log.debug("EXTRACT_REDIS_TASK devType: {}", patrolTask.getDevType()); log.debug("EXTRACT_REDIS_TASK devType: {}", patrolTask.getDevType());
if(StringUtils.isNotEmpty(patrolTask.getDevType())
// 添加可见光任务
if (StringUtils.isNotEmpty(patrolTask.getDevType())
&& patrolTask.getDevType().contains("2")) { && patrolTask.getDevType().contains("2")) {
patrolTaskListEx.add(patrolTask); patrolTaskListEx.add(patrolTask);
} }
if(StringUtils.isNotEmpty(patrolTask.getDevType())
// 添加无人机/机器狗任务
if (StringUtils.isNotEmpty(patrolTask.getDevType())
&& (patrolTask.getDevType().contains("0") || patrolTask.getDevType().contains("1"))) { && (patrolTask.getDevType().contains("0") || patrolTask.getDevType().contains("1"))) {
patrolTaskControlList.add(patrolTask); patrolTaskControlList.add(patrolTask);
} }
@ -2076,7 +2074,7 @@ public class JobMainTask {
Date date = new Date(); Date date = new Date();
log.info("JobMainTask fixedTimer clear taskInfos: {}", taskInfos.size()); log.info("JobMainTask fixedTimer clear taskInfos: {}", taskInfos.size());
taskInfos.clear(); taskInfos.clear();
Collection<String> redisKeys = redisService.keys(RedisConst.TASK_CODE_EX);
Collection<String> redisKeys = redisService.scan(RedisConst.TASK_CODE_EX);
for (String redisKey : redisKeys) { for (String redisKey : redisKeys) {
String[] keywords = StringUtils.split(redisKey, StringUtils.AT); String[] keywords = StringUtils.split(redisKey, StringUtils.AT);
if (keywords.length == 3) { if (keywords.length == 3) {
@ -2093,8 +2091,10 @@ public class JobMainTask {
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
public void execEveryDayTask() { public void execEveryDayTask() {
log.debug("***************************** JobTaskTimer execEveryDayTask *************************************"); log.debug("***************************** JobTaskTimer execEveryDayTask *************************************");
Collection<String> redisKeys = redisService.keys(RedisConst.TASK_CODE_EX);
String today = LocalDate.now().toString();
String key = RedisConst.TASK_CODE_EX + today + "*";
Collection<String> redisKeys = redisService.scan(key);
log.info("TodayTask key:{}, size:{}", key, redisKeys.size());
for (String redisKey : redisKeys) { for (String redisKey : redisKeys) {
try { try {
//redisKey = RedisConst.TASK_CODE + task.getTaskCode() + StringUtils.AT + DateUtils.format(DateUtils.yyyyMMddHHmmss2, task.getFixedStartTime()) //redisKey = RedisConst.TASK_CODE + task.getTaskCode() + StringUtils.AT + DateUtils.format(DateUtils.yyyyMMddHHmmss2, task.getFixedStartTime())


+ 81
- 0
inspect-job/src/main/java/com/inspect/job/task/ReportArchiveScheduleTask.java View File

@ -0,0 +1,81 @@
package com.inspect.job.task;
import com.inspect.base.core.web.domain.AjaxResult;
import com.inspect.job.client.ReportArchiveClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* 巡检报告定时归档任务(sys_job 在数据库灵活控制)。
* <p>
* 由 sql/sys_job_report_archive.sql 预置两条 sys_job 调用:
* <ul>
* <li>archiveMorning:每日 07:00,归档 前一日16:00 ~ 当日07:00 数据并推送平台;</li>
* <li>archiveEvening:每日 16:00,归档 当日07:00 ~ 当日16:00 数据并推送平台。</li>
* </ul>
* 窗口截止点(07:00 / 16:00)即为报告批次语义,若后续确认新的生成时间点,
* 需同时调整 sys_job 的 cron_expression 与下方 {@link #MORNING_END}/{@link #EVENING_END} 截止点。
*
* @author inspect
*/
@Slf4j
@Component("reportArchiveScheduleTask")
public class ReportArchiveScheduleTask {
/** 数据窗口截止时间:早晨批次 = 当日 07:00 */
private static final LocalTime MORNING_END = LocalTime.of(7, 0, 0);
/** 数据窗口截止时间:下午批次 = 当日 16:00 */
private static final LocalTime EVENING_END = LocalTime.of(16, 0, 0);
private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final ReportArchiveClient reportArchiveClient;
public ReportArchiveScheduleTask(ReportArchiveClient reportArchiveClient) {
this.reportArchiveClient = reportArchiveClient;
}
/**
* sys_job:每日 07:00 归档 前一日16:00 ~ 当日07:00 的巡检数据。
*/
public void archiveMorning() {
LocalDate today = LocalDate.now();
// 前一日 16:00
LocalDateTime start = LocalDateTime.of(today.minusDays(1), EVENING_END);
// 当日 07:00
LocalDateTime end = LocalDateTime.of(today, MORNING_END);
archiveByWindow("早晨批次(前一日16:00~当日07:00)", start, end);
}
/**
* sys_job:每日 16:00 归档 当日07:00 ~ 当日16:00 的巡检数据。
*/
public void archiveEvening() {
LocalDate today = LocalDate.now();
LocalDateTime start = LocalDateTime.of(today, MORNING_END);
LocalDateTime end = LocalDateTime.of(today, EVENING_END);
archiveByWindow("下午批次(当日07:00~当日16:00)", start, end);
}
/**
* 按窗口触发 inspect-main 定时归档;无待归档任务时主服务会跳过(不生成不推送)。
* 调用失败直接抛出异常,由 sys_job_log 记录失败。
*/
private void archiveByWindow(String batchName, LocalDateTime start, LocalDateTime end) {
String startTime = start.format(DATE_TIME);
String endTime = end.format(DATE_TIME);
log.info("[定时归档] {} 开始, 数据窗口: {} ~ {}", batchName, startTime, endTime);
AjaxResult result = reportArchiveClient.archiveByTime(startTime, endTime);
Object code = result == null ? null : result.get(AjaxResult.CODE_TAG);
Object msg = result == null ? null : result.get(AjaxResult.MSG_TAG);
log.info("[定时归档] {} 完成, code={}, msg={}", batchName, code, msg);
if (result == null || !(code instanceof Number) || ((Number) code).intValue() != 200) {
throw new RuntimeException("定时归档调用失败: " + (msg == null ? "无响应" : msg));
}
}
}

+ 62
- 0
inspect-main/inspect-main-agg/pom.xml View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.inspect</groupId>
<artifactId>inspect-main</artifactId>
<version>3.4.0</version>
</parent>
<artifactId>inspect-main-agg</artifactId>
<version>3.4.0</version>
<name>inspect-main-agg</name>
<description>inspect-main-agg</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 用连接池复用 TCP 连接 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.inspect</groupId>
<artifactId>inspect-base-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

+ 20
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/AggregationApplication.java View File

@ -0,0 +1,20 @@
package com.inspect.agg;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 智能巡检数据汇聚系统 - 中心采集服务(区域试点)。
*/
@SpringBootApplication
@EnableScheduling
@MapperScan("com.inspect.agg.mapper")
public class AggregationApplication {
public static void main(String[] args) {
SpringApplication.run(AggregationApplication.class, args);
}
}

+ 28
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggCalendarEvent.java View File

@ -0,0 +1,28 @@
package com.inspect.agg.calendar;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AggCalendarEvent implements Serializable {
private static final long serialVersionUID = 1L;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
private String id;
private String name;
private String status;
private String taskCode;
private String source;
private LocalDateTime startTime;
private LocalDateTime endTime;
}

+ 180
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarGenerator.java View File

@ -0,0 +1,180 @@
package com.inspect.agg.calendar;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.inspect.agg.entity.AggPatrolTask;
import com.inspect.agg.mapper.AggPatrolTaskMapper;
import com.inspect.base.core.enums.ExecStatus;
import com.inspect.base.core.enums.IntervalType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 根据汇聚方案规则生成指定月份的待执行计划。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AggTaskCalendarGenerator {
private static final String STATE_PENDING = "5";
private final AggPatrolTaskMapper taskMapper;
public List<AggCalendarEvent> generateMonth(String stationCode, int year, int month) {
YearMonth yearMonth = YearMonth.of(year, month);
LocalDate firstDay = yearMonth.atDay(1);
LocalDate lastDay = yearMonth.atEndOfMonth();
LocalDateTime monthStart = firstDay.atStartOfDay();
LocalDateTime monthEnd = lastDay.atTime(23, 59, 59);
LocalDateTime now = LocalDateTime.now();
if (monthEnd.isBefore(now)) {
return Collections.emptyList();
}
List<AggCalendarEvent> events = new ArrayList<>();
List<AggPatrolTask> tasks = taskMapper.selectEnabledTasks(stationCode);
for (AggPatrolTask task : tasks) {
if (!StringUtils.hasText(task.getDevType())) {
continue;
}
try {
if (ExecStatus.REGULAR.getCode().equals(task.getExecutionStatus())) {
generateRegular(task, monthStart, monthEnd, now, events);
} else if (ExecStatus.PERIODIC.getCode().equals(task.getExecutionStatus())) {
generatePeriodic(task, monthStart, monthEnd, now, events);
} else if (ExecStatus.INTERVAL.getCode().equals(task.getExecutionStatus())) {
generateInterval(task, monthStart, monthEnd, now, events);
}
} catch (Exception e) {
log.warn("generate calendar failed: station={}, taskId={}, error={}",
task.getStationCode(), task.getTaskId(), e.getMessage());
}
}
events.sort((a, b) -> a.getStartTime().compareTo(b.getStartTime()));
return events;
}
private void generateRegular(AggPatrolTask task, LocalDateTime monthStart, LocalDateTime monthEnd,
LocalDateTime now, List<AggCalendarEvent> events) {
LocalDateTime startTime = task.getFixedStartTime();
if (startTime != null && startTime.isAfter(now)
&& !startTime.isBefore(monthStart) && !startTime.isAfter(monthEnd)) {
events.add(event(task, startTime, null));
}
}
private void generatePeriodic(AggPatrolTask task, LocalDateTime monthStart, LocalDateTime monthEnd,
LocalDateTime now, List<AggCalendarEvent> events) {
if (task.getCycleStartTime() == null || task.getCycleEndTime() == null
|| !StringUtils.hasText(task.getCycleExecuteTime())) {
return;
}
LocalDateTime start = max(monthStart, task.getCycleStartTime());
LocalDateTime end = min(monthEnd, task.getCycleEndTime());
if (end.isBefore(start)) {
return;
}
String[] executeTimes = task.getCycleExecuteTime().split(",");
if (StringUtils.hasText(task.getCycleWeek())) {
String[] weeks = task.getCycleWeek().split(",");
for (LocalDate date = start.toLocalDate(); !date.isAfter(end.toLocalDate()); date = date.plusDays(1)) {
for (int i = 0; i < weeks.length && i < executeTimes.length; i++) {
if (date.getDayOfWeek() == parseDayOfWeek(weeks[i])) {
addIfVisible(task, date, executeTimes[i], now, end, events);
}
}
}
} else if (StringUtils.hasText(task.getCycleMonth())) {
String[] monthDays = task.getCycleMonth().split(",");
for (LocalDate date = start.toLocalDate(); !date.isAfter(end.toLocalDate()); date = date.plusDays(1)) {
for (int i = 0; i < monthDays.length && i < executeTimes.length; i++) {
if (date.getDayOfMonth() == Integer.parseInt(monthDays[i].trim())) {
addIfVisible(task, date, executeTimes[i], now, end, events);
}
}
}
}
}
private void generateInterval(AggPatrolTask task, LocalDateTime monthStart, LocalDateTime monthEnd,
LocalDateTime now, List<AggCalendarEvent> events) {
if (!IntervalType.BY_HOUR.getCode().equals(task.getIntervalType())
|| task.getIntervalNumber() == null || task.getIntervalNumber() <= 0
|| task.getIntervalStartTime() == null || task.getIntervalEndTime() == null
|| !StringUtils.hasText(task.getIntervalExecuteTime())) {
return;
}
LocalDateTime start = task.getIntervalStartTime();
LocalDateTime end = min(monthEnd, task.getIntervalEndTime());
LocalTime executeTime = LocalTime.parse(task.getIntervalExecuteTime().trim());
LocalDateTime cursor = LocalDateTime.of(start.toLocalDate(), executeTime);
while (cursor.isBefore(start)) {
cursor = cursor.plusHours(task.getIntervalNumber());
}
while (!cursor.isAfter(end)) {
if (cursor.isAfter(now) && !cursor.isBefore(monthStart)) {
events.add(event(task, cursor, null));
}
cursor = cursor.plusHours(task.getIntervalNumber());
}
}
private void addIfVisible(AggPatrolTask task, LocalDate date, String time,
LocalDateTime now, LocalDateTime end,
List<AggCalendarEvent> events) {
if (!StringUtils.hasText(time)) {
return;
}
LocalDateTime startTime = LocalDateTime.of(date, LocalTime.parse(time.trim()));
if (startTime.isAfter(now) && !startTime.isAfter(end)) {
events.add(event(task, startTime, null));
}
}
private AggCalendarEvent event(AggPatrolTask task, LocalDateTime startTime, LocalDateTime endTime) {
return AggCalendarEvent.builder()
.stationCode(task.getStationCode())
.stationName(task.getStationName())
.id(task.getTaskCode())
.taskCode(task.getTaskCode())
.name(task.getTaskName())
.status("pending")
.source("plan")
.startTime(startTime)
.endTime(endTime)
.build();
}
private static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
return a.isAfter(b) ? a : b;
}
private static LocalDateTime min(LocalDateTime a, LocalDateTime b) {
return a.isBefore(b) ? a : b;
}
private static DayOfWeek parseDayOfWeek(String value) {
String week = value == null ? "" : value.trim();
if ("星期一".equals(week) || "1".equals(week)) return DayOfWeek.MONDAY;
if ("星期二".equals(week) || "2".equals(week)) return DayOfWeek.TUESDAY;
if ("星期三".equals(week) || "3".equals(week)) return DayOfWeek.WEDNESDAY;
if ("星期四".equals(week) || "4".equals(week)) return DayOfWeek.THURSDAY;
if ("星期五".equals(week) || "5".equals(week)) return DayOfWeek.FRIDAY;
if ("星期六".equals(week) || "6".equals(week)) return DayOfWeek.SATURDAY;
if ("星期日".equals(week) || "7".equals(week)) return DayOfWeek.SUNDAY;
throw new IllegalArgumentException("不支持的星期: " + week);
}
}

+ 340
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/calendar/AggTaskCalendarService.java View File

@ -0,0 +1,340 @@
package com.inspect.agg.calendar;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.inspect.agg.entity.AggPatrolTaskStatus;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggPatrolTaskStatusMapper;
import com.inspect.agg.service.StationRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 汇聚计划服务:规则生成未来计划,合并实际任务状态,并使用 Redis 缓存。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AggTaskCalendarService {
private static final String CACHE_PREFIX = "agg:calendar:v2:";
private static final Duration CACHE_TTL = Duration.ofHours(12);
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final TypeReference<List<AggCalendarEvent>> EVENT_LIST_TYPE =
new TypeReference<List<AggCalendarEvent>>() { };
private static final TypeReference<List<Map<String, Object>>> STATS_LIST_TYPE =
new TypeReference<List<Map<String, Object>>>() { };
private final AggTaskCalendarGenerator calendarGenerator;
private final AggPatrolTaskStatusMapper taskStatusMapper;
private final StationRegistry stationRegistry;
private final StringRedisTemplate redisTemplate;
private final ObjectMapper objectMapper;
public List<Map<String, Object>> year(String stationCode, int year) {
String scope = scope(stationCode);
String cacheKey = CACHE_PREFIX + "summary:" + scope + ":" + year;
List<Map<String, Object>> cached = readCache(cacheKey, STATS_LIST_TYPE);
if (cached != null) {
return cached;
}
List<Map<String, Object>> result = new ArrayList<>();
for (int month = 1; month <= 12; month++) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("month", month);
item.put("executed", 0L);
item.put("executing", 0L);
item.put("pending", 0L);
item.put("total", 0L);
result.add(item);
}
for (int month = 1; month <= 12; month++) {
List<AggCalendarEvent> events = visibleEvents(loadMonthEvents(stationCode, year, month));
Map<String, Object> item = result.get(month - 1);
long executed = count(events, "executed");
long executing = count(events, "executing");
long pending = count(events, "pending");
item.put("executed", executed);
item.put("executing", executing);
item.put("pending", pending);
item.put("total", executed + executing + pending);
}
writeCache(cacheKey, result, summaryTtl(year));
return result;
}
public List<Map<String, Object>> month(String stationCode, int year, int month) {
List<AggCalendarEvent> events = visibleEvents(loadMonthEvents(stationCode, year, month));
applyStationNames(events);
return toMaps(events);
}
public List<Map<String, Object>> day(String stationCode, int year, int month, int day) {
LocalDate target = LocalDate.of(year, month, day);
List<AggCalendarEvent> result = new ArrayList<>();
for (AggCalendarEvent event : visibleEvents(loadMonthEvents(stationCode, year, month))) {
if (event.getStartTime() != null && target.equals(event.getStartTime().toLocalDate())) {
result.add(event);
}
}
applyStationNames(result);
return toMaps(result);
}
public void evictStation(String stationCode) {
if (!StringUtils.hasText(stationCode)) {
return;
}
YearMonth now = YearMonth.now();
for (int i = -12; i <= 12; i++) {
YearMonth month = now.plusMonths(i);
deleteQuietly(monthKey(stationCode, month.getYear(), month.getMonthValue()));
}
deleteQuietly(CACHE_PREFIX + "summary:" + stationCode + ":" + now.getYear());
deleteQuietly(CACHE_PREFIX + "summary:" + stationCode + ":" + (now.getYear() + 1));
evictAllScopes();
}
@Scheduled(cron = "0 50 0 * * ?")
public void refreshFutureCalendar() {
YearMonth current = YearMonth.now();
for (AggStation station : stationRegistry.listEnabled()) {
for (int i = 0; i < 6; i++) {
YearMonth month = current.plusMonths(i);
deleteQuietly(monthKey(station.getStationCode(), month.getYear(), month.getMonthValue()));
loadStationMonthCached(station.getStationCode(), month.getYear(), month.getMonthValue());
}
}
evictAllScopes();
}
private List<AggCalendarEvent> loadMonthEvents(String stationCode, int year, int month) {
String scope = scope(stationCode);
String key = monthKey(stationCode, year, month);
List<AggCalendarEvent> cached = readCache(key, EVENT_LIST_TYPE);
if (cached != null) {
return cached;
}
List<AggCalendarEvent> result = new ArrayList<>();
if (StringUtils.hasText(stationCode)) {
result.addAll(loadStationMonth(stationCode, year, month));
} else {
for (AggStation station : stationRegistry.listEnabled()) {
result.addAll(loadStationMonthCached(station.getStationCode(), year, month));
}
result = merge(result);
}
writeCache(key, result, monthTtl(year, month));
return result;
}
private List<AggCalendarEvent> loadStationMonthCached(String stationCode, int year, int month) {
String key = monthKey(stationCode, year, month);
List<AggCalendarEvent> cached = readCache(key, EVENT_LIST_TYPE);
if (cached != null) {
return cached;
}
List<AggCalendarEvent> result = loadStationMonth(stationCode, year, month);
writeCache(key, result, monthTtl(year, month));
return result;
}
private List<AggCalendarEvent> loadStationMonth(String stationCode, int year, int month) {
List<AggCalendarEvent> events = new ArrayList<>();
events.addAll(calendarGenerator.generateMonth(stationCode, year, month));
List<AggPatrolTaskStatus> actualTasks =
taskStatusMapper.selectDailyStatsByYearAndMonth(stationCode, year, month);
for (AggPatrolTaskStatus task : actualTasks) {
if (task.getStartTime() == null) {
continue;
}
events.add(AggCalendarEvent.builder()
.stationCode(task.getStationCode())
.stationName(task.getStationName())
.id(task.getTaskPatrolledId())
.taskCode(task.getTaskCode())
.name(task.getTaskName())
.status(stateName(task.getTaskState()))
.source("actual")
.startTime(task.getStartTime())
.endTime(task.getEndTime())
.build());
}
return merge(events);
}
private static List<AggCalendarEvent> merge(List<AggCalendarEvent> events) {
Map<String, AggCalendarEvent> unique = new HashMap<>();
for (AggCalendarEvent event : events) {
if (event.getStartTime() == null) {
continue;
}
String key = event.getStationCode() + "|" + event.getTaskCode() + "|" + event.getStartTime();
AggCalendarEvent existing = unique.get(key);
if (existing == null || "actual".equals(event.getSource())) {
unique.put(key, event);
}
}
List<AggCalendarEvent> result = new ArrayList<>(unique.values());
result.sort((a, b) -> a.getStartTime().compareTo(b.getStartTime()));
return result;
}
private List<AggCalendarEvent> visibleEvents(List<AggCalendarEvent> events) {
LocalDateTime now = LocalDateTime.now();
List<AggCalendarEvent> result = new ArrayList<>();
for (AggCalendarEvent event : events) {
if ("plan".equals(event.getSource())
&& event.getStartTime() != null && !event.getStartTime().isAfter(now)) {
continue;
}
result.add(event);
}
return result;
}
private List<Map<String, Object>> toMaps(List<AggCalendarEvent> events) {
List<Map<String, Object>> result = new ArrayList<>();
for (AggCalendarEvent event : events) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("id", event.getId());
item.put("name", event.getName());
item.put("status", event.getStatus());
item.put("start", event.getStartTime() == null ? null : event.getStartTime().format(DATE_FORMATTER));
item.put("end", event.getEndTime() == null ? null : event.getEndTime().format(DATE_FORMATTER));
item.put("startTime", event.getStartTime() == null ? null : event.getStartTime().format(DATE_TIME_FORMATTER));
item.put("endTime", event.getEndTime() == null ? null : event.getEndTime().format(DATE_TIME_FORMATTER));
item.put("stationCode", event.getStationCode());
item.put("stationName", event.getStationName() != null ? event.getStationName() : event.getStationCode());
result.add(item);
}
return result;
}
private void applyStationNames(List<AggCalendarEvent> events) {
if (events == null || events.isEmpty()) {
return;
}
boolean needLookup = false;
for (AggCalendarEvent event : events) {
if (StringUtils.hasText(event.getStationCode()) && !StringUtils.hasText(event.getStationName())) {
needLookup = true;
break;
}
}
if (!needLookup) {
return;
}
Map<String, String> stationNames = new HashMap<>();
for (AggStation station : stationRegistry.listAll()) {
stationNames.put(station.getStationCode(), station.getStationName());
}
for (AggCalendarEvent event : events) {
if (StringUtils.hasText(event.getStationCode()) && !StringUtils.hasText(event.getStationName())) {
event.setStationName(stationNames.getOrDefault(event.getStationCode(), event.getStationCode()));
}
}
}
private static long count(List<AggCalendarEvent> events, String status) {
long count = 0;
for (AggCalendarEvent event : events) {
if (status.equals(event.getStatus())) {
count++;
}
}
return count;
}
private static String stateName(String state) {
if ("1".equals(state)) return "executed";
if ("2".equals(state)) return "executing";
if ("3".equals(state)) return "paused";
if ("4".equals(state)) return "terminate";
if ("5".equals(state)) return "pending";
if ("6".equals(state)) return "exceeded";
return null;
}
private String monthKey(String stationCode, int year, int month) {
return CACHE_PREFIX + "events:" + scope(stationCode) + ":"
+ year + "-" + String.format("%02d", month);
}
private static String scope(String stationCode) {
return StringUtils.hasText(stationCode) ? stationCode : "ALL";
}
private <T> T readCache(String key, TypeReference<T> type) {
try {
String value = redisTemplate.opsForValue().get(key);
return StringUtils.hasText(value) ? objectMapper.readValue(value, type) : null;
} catch (Exception e) {
log.warn("read calendar cache failed: key={}, error={}", key, e.getMessage());
return null;
}
}
private void writeCache(String key, Object value, Duration ttl) {
try {
redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(value), ttl);
} catch (Exception e) {
log.warn("write calendar cache failed: key={}, error={}", key, e.getMessage());
}
}
private static Duration monthTtl(int year, int month) {
YearMonth current = YearMonth.now();
YearMonth target = YearMonth.of(year, month);
if (target.equals(current)) return Duration.ofMinutes(1);
if (target.equals(current.plusMonths(1))) return Duration.ofHours(1);
return CACHE_TTL;
}
private static Duration summaryTtl(int year) {
return year == YearMonth.now().getYear() ? Duration.ofMinutes(5) : CACHE_TTL;
}
private void deleteQuietly(String key) {
try {
redisTemplate.delete(key);
} catch (Exception e) {
log.warn("delete calendar cache failed: key={}, error={}", key, e.getMessage());
}
}
private void evictAllScopes() {
YearMonth current = YearMonth.now();
for (int i = 0; i < 6; i++) {
YearMonth month = current.plusMonths(i);
deleteQuietly(monthKey(null, month.getYear(), month.getMonthValue()));
}
deleteQuietly(CACHE_PREFIX + "summary:ALL:" + current.getYear());
deleteQuietly(CACHE_PREFIX + "summary:ALL:" + (current.getYear() + 1));
}
}

+ 111
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/AggProperties.java View File

@ -0,0 +1,111 @@
package com.inspect.agg.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 汇聚采集服务配置(prefix = agg):默认值写在代码中;application.yml 已无 agg.* 配置项。
* 站点配置已迁移到 DB 表 agg_station(见 StationRegistry),不再放 yaml。
*/
@Data
@Component
@ConfigurationProperties(prefix = "agg")
public class AggProperties {
private Sync sync = new Sync();
private Proxy proxy = new Proxy();
private Video video = new Video();
private File file = new File();
private Http http = new Http();
private Statistic statistic = new Statistic();
@Data
public static class Sync {
/** 基础数据同步公共配置(轮询周期见 SyncDefaults,已写死在代码) */
private Base base = new Base();
private Dynamic dynamic = new Dynamic();
private AnalysisSummary analysisSummary = new AnalysisSummary();
@Data
public static class Base {
private int pageSize = 100;
/** 启动时先同步一次(联调用) */
private boolean runOnStart = false;
}
@Data
public static class Dynamic {
/** 启动时先同步一次 */
private boolean runOnStart = false;
}
@Data
public static class AnalysisSummary {
private boolean runOnStart = true;
}
}
@Data
public static class Proxy {
/** 允许代理的路径前缀(为空=不限制);建议按需收紧,如 patrol/、v1/ */
private java.util.List<String> allowedPrefixes = new java.util.ArrayList<>();
}
@Data
public static class Video {
/** 集控 ZLMediaKit API/播放地址(如 http://192.168.1.116:33380) */
private String zlmBaseUrl = "http://192.168.1.116:33380";
/** ZLM config.ini 的 api.secret */
private String zlmSecret = "Xg@123456";
/** 拉流目标 app */
private String app = "live";
/** 播放地址对办公网络暴露的地址(默认同 zlmBaseUrl) */
private String publicBaseUrl = "";
/** 站端 ZLM 直连流的集控 nginx 地址;留空时按当前请求 Host 自动拼接 */
private String nginxBaseUrl = "";
/** 是否启用 RTSP TCP 传输(0=TCP, 1=UDP) */
private int rtspTransport = 0;
/** 拉流重试次数(-1=无限) */
private int retryCount = -1;
/** ZLM hook 接收接口 token(内部鉴权) */
private String hookToken = "inspect-agg-hook";
/** 无 HLS 请求多少毫秒后视为空闲 */
private long hookIdleTimeoutMs = 30_000L;
}
@Data
public static class File {
/** 站端图片/文件反代的集控 nginx 地址;留空时按当前请求 Host 自动拼接 */
private String nginxBaseUrl = "";
/** 是否在 /agg/proxy 通用代理的 JSON 响应中递归改写图片地址 */
private boolean rewriteProxyResponse = true;
}
@Data
public static class Http {
private int connectTimeoutMs = 10_000;
private int readTimeoutMs = 30_000;
/**
* 连接池总连接上限。
*/
private int poolMaxTotal = 100;
/** 同一站端的连接上限;一次九画面并发起流按 3 路一批,留足余量。 */
private int poolMaxPerRoute = 40;
/** 从连接池取空闲连接的等待上限(毫秒),避免池满时无限等待。 */
private int connectionRequestTimeoutMs = 3_000;
}
@Data
public static class Statistic {
/** 是否统计轻微缺陷;false 时 minorCount 恒为 0,且不计入 defectCount */
private boolean minorDefectEnabled = false;
}
}

+ 83
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/RestTemplateConfig.java View File

@ -0,0 +1,83 @@
package com.inspect.agg.config;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
/**
* HTTP 客户端配置:连接池 + 超时 + UTF-8(站点接口返回中文,必须 UTF-8 解码)。
*
* <p>聚合层向站端发起的起流/停流请求在打开多画面时会同时出现十几个,串行新建 TCP
* 连接会明显放大延迟并让部分请求打满页面 8 秒超时;这里改为复用连接的连接池。</p>
*/
@Configuration
public class RestTemplateConfig {
private final AggProperties aggProperties;
/** 显式构造器注入(不用 Lombok,避免 IDE 静态检查误报找不到 Bean) */
public RestTemplateConfig(AggProperties aggProperties) {
this.aggProperties = aggProperties;
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
AggProperties.Http http = aggProperties.getHttp();
// 连接池:总连接数与单站端连接数都要覆盖“同时在飞的站端请求”。
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(Math.max(1, http.getPoolMaxTotal()));
connectionManager.setDefaultMaxPerRoute(Math.max(1, http.getPoolMaxPerRoute()));
// 超时与池等待:取连接不能无限等,否则池满时请求会一直挂到页面超时。
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(http.getConnectTimeoutMs())
.setSocketTimeout(http.getReadTimeoutMs())
.setConnectionRequestTimeout(http.getConnectionRequestTimeoutMs())
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
// 定期清理失效空闲连接,避免站端重启后一直复用坏连接。
.evictExpiredConnections()
.evictIdleConnections(30L, TimeUnit.SECONDS)
.build();
// 只在这里指定请求工厂;连接与读取超时统一由上面的 RequestConfig 控制,
// 避免与 RestTemplateBuilder 的超时设置叠加产生歧义。
RestTemplate restTemplate = builder
.requestFactory(() -> {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(http.getConnectTimeoutMs());
factory.setReadTimeout(http.getReadTimeoutMs());
return factory;
})
.build();
// 默认 StringHttpMessageConverter 用 ISO-8859-1,会破坏站点返回的中文
List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof StringHttpMessageConverter) {
StringHttpMessageConverter stringConverter = (StringHttpMessageConverter) converter;
stringConverter.setDefaultCharset(StandardCharsets.UTF_8);
}
}
return restTemplate;
}
}

+ 19
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/SyncDefaults.java View File

@ -0,0 +1,19 @@
package com.inspect.agg.config;
/**
* 同步相关默认配置(写死在代码内;如需调整改这里并重新构建)。
*/
public final class SyncDefaults {
/** 基础数据全量轮询周期(毫秒):5 分钟 */
public static final String BASE_INTERVAL_MS = "300000";
/** 任务执行活跃子集轮询周期(毫秒):60 秒 */
public static final String DYNAMIC_INTERVAL_MS = "60000";
/** 动态数据全量对账周期(毫秒):5 分钟 */
public static final String DYNAMIC_FULL_INTERVAL_MS = "300000";
private SyncDefaults() {
}
}

+ 38
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/dto/AggMonitorPatrolPointVo.java View File

@ -0,0 +1,38 @@
package com.inspect.agg.dto;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* Response item for /agg/patrolpoint/list/monitor.
* The patrolpoint* JSON names match the station-side API contract.
*/
@Data
public class AggMonitorPatrolPointVo {
private Long id;
private String stationCode;
private String stationName;
private Long patrolpointId;
private String patrolpointCode;
private String patrolpointName;
private Long mainDeviceId;
private String deviceName;
private Long deviceId;
private Long areaId;
private String areaName;
private String patrolContent;
private String purpose;
private String patrolTypeId;
private String pointImptLevel;
private String alarmThreshold;
private String algSubtypeIds;
private String algSubtypeName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 15
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/dto/VideoRequest.java View File

@ -0,0 +1,15 @@
package com.inspect.agg.dto;
import lombok.Data;
/**
* 汇聚视频接口请求体。
*/
@Data
public class VideoRequest {
private String cameraCode;
private Integer streamType = 2;
private Integer direct = 0;
private String rtspUrl;
}

+ 13
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggAlgSubtype.java View File

@ -0,0 +1,13 @@
package com.inspect.agg.entity;
import lombok.Data;
/**
* Shared station algorithm subtype dictionary (read-only).
*/
@Data
public class AggAlgSubtype {
private Long algSubtypeId;
private String algSubtypeCode;
private String algSubtypeName;
}

+ 26
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggAnalysisDefectMonth.java View File

@ -0,0 +1,26 @@
package com.inspect.agg.entity;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 站端缺陷月度汇总表。
*/
@Data
public class AggAnalysisDefectMonth {
private Long id;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
private LocalDate statMonth;
private String pointType;
private Integer minorCount;
private Integer generalCount;
private Integer seriousCount;
private Integer urgentCount;
private Integer normalCount;
private LocalDateTime syncedAt;
}

+ 43
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataArea.java View File

@ -0,0 +1,43 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 区域镜像表 agg_basedata_area(源表:站点 basedata_area)。
*/
@Data
public class AggBasedataArea {
private Long id;
/** 站点编码 */
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
/** 源区域 id */
private Long areaId;
/** 父区域 id */
private Long parentId;
/** 祖级列表,如 ,1,2002 */
private String ancestors;
private String areaName;
private String areaCode;
private String areaAddr;
/** station / station_in / bayunit */
private String areaType;
private String deviceTypeCode;
private Integer orderNum;
/** 0正常 1停用 */
private String status;
private String remark;
/** 站点侧 update_time */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
/** 中心落库时间 */
private LocalDateTime syncedAt;
/** 0存在 1站点侧已删除 */
private Integer isDeleted;
}

+ 42
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataDevice.java View File

@ -0,0 +1,42 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 设备镜像表 agg_basedata_device(源表:站点 basedata_device,接口 /basedata/prodevmnt/device/list)。
*/
@Data
public class AggBasedataDevice {
private Long id;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
/** 源设备 id */
private Long deviceId;
/** 父设备 id */
private Long parentId;
private String ancestors;
private Long areaId;
private String areaName;
private String deviceCode;
private String deviceName;
private String deviceTypeName;
private String deviceModel;
private String manufacturer;
/** 电力主设备标识 Yes/No */
private String powerMainEquFlag;
private String stationNo;
private String monitorIndexNo;
/** 0正常 1停用 */
private String status;
private Integer orderNum;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 37
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataDevicetype.java View File

@ -0,0 +1,37 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 设备类型镜像表 agg_basedata_devicetype(源表:站点 basedata_devicetype,接口 /basedata/devicetype/list)。
*/
@Data
public class AggBasedataDevicetype {
private Long id;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
/** 源设备类型 id */
private Long deviceTypeId;
private Long parentId;
private Integer orderNum;
private String ancestors;
private String ancestorsAddr;
private String deviceTypeName;
private String deviceTypeCode;
private String powerMainEquFlag;
private String phaseNum;
private String phase;
private String status;
private String professionType;
private Long level;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 42
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataEqpbook.java View File

@ -0,0 +1,42 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 设备台账镜像表 agg_basedata_eqpbook(源表:站点 basedata_eqpbook,接口 /basedata/eqpbook/list)。
* 注意:源响应含 user/password 等敏感字段,本表不镜像。
*/
@Data
public class AggBasedataEqpbook {
private Long id;
private String stationCode;
/** 源台账 id */
private Long eqpbookId;
/** 汇聚自定义显示顺序 */
private Integer orderNum;
private String patrolDeviceCode;
private String patrolDeviceName;
private String stationName;
private Long areaId;
private String areaName;
private String deviceSource;
private String deviceModel;
private String manufacturer;
/** 设备类型(type) */
private String deviceType;
private String ipAddr;
private String port;
private String videoNvrCode;
private String mainSystemCode;
private String useMode;
private String videoMode;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 47
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataEqpbookChannel.java View File

@ -0,0 +1,47 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 设备通道镜像表 agg_basedata_eqpbook_channel(源表:站点 basedata_eqpbook_channel,接口 /basedata/eqpbook/channel/list)。
*/
@Data
public class AggBasedataEqpbookChannel {
private Long id;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
/** 源通道 id */
private Long channelId;
/** 汇聚自定义显示顺序 */
private Integer orderNum;
private String channelCode;
private String channelName;
/** vl/ir/ptz/ng */
private String channelType;
/** 1在线 0离线 */
private String onlineState;
private String channelContent;
private String host;
private String port;
private Long areaId;
private String areaName;
/** 所属台账 id */
private Long eqpbookId;
private String patrolDeviceCode;
private String upSystemDeviceCode;
private String deviceSource;
/** 通道能力(JSON 字符串,树接口返回时解析为对象) */
private String capabilities;
/** 使用类型(源表 basedata_eqpbook_channel.use_mode) */
private String useMode;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 44
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggBasedataPatrolpoint.java View File

@ -0,0 +1,44 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 巡检点位镜像表 agg_basedata_patrolpoint(源表:站点 basedata_patrolpoint,接口 /basedata/patrolpointmnt/patrolpoint/list)。
*/
@Data
public class AggBasedataPatrolpoint {
private Long id;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
/** 源点位 id */
private Long patrolPointId;
private String patrolPointCode;
private String patrolPointName;
/** 主设备 id */
private Long mainDeviceId;
private String deviceName;
/** 设备 id(真正配置点位的设备/部件) */
private Long deviceId;
private Long areaId;
private String areaName;
private String patrolContent;
private String purpose;
/** 巡视类型,逗号分隔 */
private String patrolTypeId;
/** 重要等级 Ⅰ/Ⅱ/Ⅲ */
private String pointImptLevel;
/** 告警阈值 JSON */
private String alarmThreshold;
/** Source algorithm subtype IDs, comma-separated. */
private String algSubtypeIds;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 64
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggDefectRecord.java View File

@ -0,0 +1,64 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
/**
* Snapshot row returned by station-side /patrol/analysis/defect/list.
*/
@Data
public class AggDefectRecord {
@JsonIgnore
private Long id;
private String stationCode;
private String stationName;
private String deviceName;
private String channelName;
private String channelCode;
private String deviceSource;
private String pointName;
private String pointId;
private String warnStatus;
private String algName;
private String resValue;
private String img;
private String filterDesc;
private String brightDesc;
private String imgAnalyse;
private String brightImgAnalyse;
private String desc;
private String lineId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime patrolTime;
private String pointStatus;
private String imgType;
private String dataType;
private String filter;
private String requestId;
private String resStatus;
private String brightResStatus;
private String areaName;
private String areaId;
private String algType;
private String objectId;
private String imageNormalUrlPath;
private String filePath;
private String suggestion;
private String originalValue;
private String taskPatrolId;
private String taskName;
private Integer defectLevel;
@JsonIgnore
private LocalDateTime syncedAt;
@JsonIgnore
private Integer isDeleted;
}

+ 50
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggInspectionReport.java View File

@ -0,0 +1,50 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 巡视报告镜像表 agg_inspection_report。
*/
@Data
public class AggInspectionReport {
private Long id;
private String stationCode;
private Long lineId;
private String stationName;
private String voltLevel;
private String stationType;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private LocalDateTime inspectionDate;
private String inspectionTaskName;
private String envInfo;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime inspectionStartTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime inspectionEndTime;
private String patrolStatistics;
private String checkPerson;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime checkTime;
private String description;
private String filePath;
private String taskId;
private String taskResultId;
private String taskPatrolledId;
private String location;
private String filter;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 65
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggPatrolTask.java View File

@ -0,0 +1,65 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 任务方案镜像表 agg_patrol_task(源:patrol_task,接口 /patrol/task/list)。
*/
@Data
public class AggPatrolTask {
private Long id;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
/** 源任务 id */
private Long taskId;
private String taskCode;
private String taskName;
/** 巡视类型 */
private String type;
private String priority;
private String deviceLevel;
private String deviceList;
private LocalDateTime fixedStartTime;
/** 0可用 1不可用 */
private String isEnable;
/** 状态 */
private String fileStatus;
/** 执行状态 */
private String executionStatus;
private String taskCycle;
private String cycleMonth;
private String cycleWeek;
private String cycleExecuteTime;
private LocalDateTime cycleStartTime;
private LocalDateTime cycleEndTime;
private Integer intervalNumber;
private String intervalType;
private String intervalExecuteTime;
private LocalDateTime intervalStartTime;
private LocalDateTime intervalEndTime;
private LocalDateTime invalidStartTime;
private LocalDateTime invalidEndTime;
/** 1机器人 0无人机 2视频 */
private String devType;
private String areaId;
private String areaName;
/** 字典 point_alg_category 的 dict_code,多个值逗号分隔 */
private String pointType;
/** 方案点位总数,来源:站端 /patrol/task/list 统计返回 */
private Integer totalPoints;
private String executionMode;
private String drafter;
private String specifiedAlg;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 49
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggPatrolTaskStatus.java View File

@ -0,0 +1,49 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 任务执行状态镜像表 agg_patrol_task_status(源:patrol_task_status,接口 /patrol/taskstatus/list)。
*/
@Data
public class AggPatrolTaskStatus {
private Long id;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
private Long lineId;
private String taskPatrolledId;
private String taskName;
private String taskCode;
/** 1已执行 2执行中 3暂停 4终止 5未执行 6超期 */
private String taskState;
private LocalDateTime planStartTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime endTime;
private String taskProgress;
private String taskEstimatedTime;
private String patrolType;
private String posType;
private String code;
private String pos;
private String execType;
private String taskType;
private String mainId;
/** ??????????? /patrol/taskstatus/list ???? */
private Integer qxNum;
/** ?????0??? 1????????? /patrol/taskstatus/list ???? */
private String fileStatus;
private String summary;
private String description;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 45
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggResultAnalysis.java View File

@ -0,0 +1,45 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* result_analysis 汇聚镜像表。
*/
@Data
public class AggResultAnalysis {
private Long id;
private String stationCode;
/** 仅用于展示:所属站点名称,不落业务表 */
private String stationName;
private Long lineId;
private String resultType;
private String businessId;
private String resultContent;
private String resStatus;
private String pointStatus;
private String suggestion;
private String objectId;
private String description;
private String requestId;
private String taskPatrolId;
private String algType;
private String resValue;
private String resCode;
private String resConf;
private String resDesc;
private String result;
private String resImgUrl;
private String imageNormalUrlPath;
private String resPos;
private String filter;
private String originalValue;
private Integer defectLevel;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
private LocalDateTime syncedAt;
private Integer isDeleted;
}

+ 33
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggStation.java View File

@ -0,0 +1,33 @@
package com.inspect.agg.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
/**
* 站点配置表 agg_station(DB 维护,替代 yaml 站点列表)。
* 密码临时明文存储。
*/
@Data
public class AggStation {
private Long id;
/** 站点编码(中心侧唯一) */
private String stationCode;
private String stationName;
/** 站点 API 根地址,以 / 结尾 */
@JsonIgnore
private String baseUrl;
/** 站端门户网址 */
private String portalUrl;
@JsonIgnore
private String username;
/** 明文密码(临时;正式化前轮换并改加密/密钥) */
@JsonIgnore
private String password;
/** 1启用 0停用 */
private String enabled;
/** 汇聚自定义显示顺序(数据库手工维护) */
private Integer orderNum;
}

+ 27
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/entity/AggSyncLog.java View File

@ -0,0 +1,27 @@
package com.inspect.agg.entity;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 同步运行日志(表 agg_sync_log)。
*/
@Data
public class AggSyncLog {
private Long id;
private String stationCode;
/** 同步实体:area/device/point/eqpbook/channel/devicetype/task/taskplan */
private String entity;
/** FULL / ACTIVE */
private String syncType;
/** SUCCESS / FAILED */
private String status;
private Integer rowCount;
private Integer deletedCount;
private String message;
private LocalDateTime startedAt;
private LocalDateTime finishedAt;
}

+ 37
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/enums/AlgCategory.java View File

@ -0,0 +1,37 @@
package com.inspect.agg.enums;
import org.springframework.util.StringUtils;
public enum AlgCategory {
INFRARED("882", "红外"),
METER("881", "表计"),
APPEARANCE("880", "外观");
private final String code;
private final String label;
AlgCategory(String code, String label) {
this.code = code;
this.label = label;
}
public String getCode() {
return code;
}
public String getLabel() {
return label;
}
public static AlgCategory fromLabel(String label) {
if (!StringUtils.hasText(label)) {
return null;
}
for (AlgCategory category : values()) {
if (category.label.equals(label.trim())) {
return category;
}
}
throw new IllegalArgumentException("不支持的算法分类: " + label);
}
}

+ 25
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggAnalysisDefectMonthMapper.java View File

@ -0,0 +1,25 @@
package com.inspect.agg.mapper;
import java.time.LocalDate;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggAnalysisDefectMonth;
@Mapper
public interface AggAnalysisDefectMonthMapper {
int upsert(AggAnalysisDefectMonth item);
int deleteRange(@Param("stationCode") String stationCode,
@Param("beginMonth") LocalDate beginMonth,
@Param("endMonth") LocalDate endMonth);
List<AggAnalysisDefectMonth> selectRange(@Param("stationCode") String stationCode,
@Param("beginMonth") LocalDate beginMonth,
@Param("endMonth") LocalDate endMonth);
long countAll();
}

+ 30
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataAreaMapper.java View File

@ -0,0 +1,30 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggBasedataArea;
/**
* AggBasedataArea 镜像 Mapper:custom SQL 全部在 XML(resources/mapper/AggBasedataAreaMapper.xml),无 QueryWrapper。
*/
@Mapper
public interface AggBasedataAreaMapper {
/** 幂等 upsert:按 (station_code, 源主键) */
int upsert(AggBasedataArea e);
/** 全量对账:本次未出现的行标记 is_deleted=1 */
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
/** 列表(限制条数,limit<=0 表示不限制) */
List<AggBasedataArea> listByStation(@Param("stationCode") String stationCode, @Param("limit") int limit);
/** 整站区域(用于组树) */
List<AggBasedataArea> listForTree(@Param("stationCode") String stationCode);
long countByStation(@Param("stationCode") String stationCode);
}

+ 26
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataDeviceMapper.java View File

@ -0,0 +1,26 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggBasedataDevice;
/**
* AggBasedataDevice 镜像 Mapper:custom SQL 全部在 XML(resources/mapper/AggBasedataDeviceMapper.xml),无 QueryWrapper。
*/
@Mapper
public interface AggBasedataDeviceMapper {
/** 幂等 upsert:按 (station_code, 源主键) */
int upsert(AggBasedataDevice e);
/** 全量对账:本次未出现的行标记 is_deleted=1 */
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
List<AggBasedataDevice> list(@Param("stationCode") String stationCode, @Param("areaId") Long areaId, @Param("limit") int limit);
long count(@Param("stationCode") String stationCode, @Param("areaId") Long areaId);
}

+ 26
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataDevicetypeMapper.java View File

@ -0,0 +1,26 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggBasedataDevicetype;
/**
* AggBasedataDevicetype 镜像 Mapper:custom SQL 全部在 XML(resources/mapper/AggBasedataDevicetypeMapper.xml),无 QueryWrapper。
*/
@Mapper
public interface AggBasedataDevicetypeMapper {
/** 幂等 upsert:按 (station_code, 源主键) */
int upsert(AggBasedataDevicetype e);
/** 全量对账:本次未出现的行标记 is_deleted=1 */
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
List<AggBasedataDevicetype> list(@Param("stationCode") String stationCode, @Param("limit") int limit);
long count(@Param("stationCode") String stationCode);
}

+ 28
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataEqpbookChannelMapper.java View File

@ -0,0 +1,28 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggBasedataEqpbookChannel;
/**
* AggBasedataEqpbookChannel 闀滃儚 Mapper锛歝ustom SQL 鍏ㄩ儴鍦?XML锛坮esources/mapper/AggBasedataEqpbookChannelMapper.xml锛夛紝鏃?QueryWrapper銆? */
@Mapper
public interface AggBasedataEqpbookChannelMapper {
/** 骞傜瓑 upsert锛氭寜 (station_code, 婧愪富閿? */
int upsert(AggBasedataEqpbookChannel e);
/** 鍏ㄩ噺瀵硅处锛氭湰娆℃湭鍑虹幇鐨勮鏍囪 is_deleted=1 */
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
List<AggBasedataEqpbookChannel> list(@Param("stationCode") String stationCode, @Param("limit") int limit);
long count(@Param("stationCode") String stationCode);
List<Map<String, Object>> countByDeviceSource(@Param("stationCode") String stationCode);
}

+ 26
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataEqpbookMapper.java View File

@ -0,0 +1,26 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggBasedataEqpbook;
/**
* AggBasedataEqpbook 镜像 Mapper:custom SQL 全部在 XML(resources/mapper/AggBasedataEqpbookMapper.xml),无 QueryWrapper。
*/
@Mapper
public interface AggBasedataEqpbookMapper {
/** 幂等 upsert:按 (station_code, 源主键) */
int upsert(AggBasedataEqpbook e);
/** 全量对账:本次未出现的行标记 is_deleted=1 */
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
List<AggBasedataEqpbook> list(@Param("stationCode") String stationCode, @Param("limit") int limit);
long count(@Param("stationCode") String stationCode);
}

+ 31
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggBasedataPatrolpointMapper.java View File

@ -0,0 +1,31 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.dto.AggMonitorPatrolPointVo;
import com.inspect.agg.entity.AggBasedataPatrolpoint;
/**
* AggBasedataPatrolpoint 镜像 Mapper:custom SQL 全部在 XML(resources/mapper/AggBasedataPatrolpointMapper.xml),无 QueryWrapper。
*/
@Mapper
public interface AggBasedataPatrolpointMapper {
/** 幂等 upsert:按 (station_code, 源主键) */
int upsert(AggBasedataPatrolpoint e);
/** 全量对账:本次未出现的行标记 is_deleted=1 */
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
List<AggBasedataPatrolpoint> list(@Param("stationCode") String stationCode, @Param("deviceId") Long deviceId,
@Param("areaId") Long areaId, @Param("limit") int limit);
long count(@Param("stationCode") String stationCode, @Param("deviceId") Long deviceId, @Param("areaId") Long areaId);
List<AggMonitorPatrolPointVo> selectMonitorRelationList(@Param("stationCode") String stationCode,
@Param("deviceId") Long deviceId);
}

+ 26
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggDefectRecordMapper.java View File

@ -0,0 +1,26 @@
package com.inspect.agg.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggDefectRecord;
@Mapper
public interface AggDefectRecordMapper {
int upsert(AggDefectRecord record);
List<AggDefectRecord> selectActiveKeys(@Param("stationCode") String stationCode);
int markDeletedByIds(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
List<AggDefectRecord> selectList(@Param("stationCode") String stationCode,
@Param("pointName") String pointName,
@Param("beginTime") String beginTime,
@Param("endTime") String endTime,
@Param("algName") String algName,
@Param("defectLevel") Integer defectLevel);
List<String> selectDistinctAlgNames(@Param("stationCode") String stationCode);
}

+ 26
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggInspectionReportMapper.java View File

@ -0,0 +1,26 @@
package com.inspect.agg.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggInspectionReport;
@Mapper
public interface AggInspectionReportMapper {
int upsert(AggInspectionReport report);
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("lineIds") List<Long> lineIds);
List<AggInspectionReport> selectList(@Param("stationCode") String stationCode,
@Param("cycleStartTime") String cycleStartTime,
@Param("cycleEndTime") String cycleEndTime,
@Param("inspectionTaskName") String inspectionTaskName);
long count(@Param("stationCode") String stationCode,
@Param("cycleStartTime") String cycleStartTime,
@Param("cycleEndTime") String cycleEndTime,
@Param("inspectionTaskName") String inspectionTaskName);
}

+ 31
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggPatrolTaskMapper.java View File

@ -0,0 +1,31 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggPatrolTask;
/**
* AggPatrolTask 镜像 Mapper:custom SQL 全部在 XML(resources/mapper/AggPatrolTaskMapper.xml),无 QueryWrapper。
*/
@Mapper
public interface AggPatrolTaskMapper {
/** 幂等 upsert:按 (station_code, 源主键) */
int upsert(AggPatrolTask e);
/** 全量对账:本次未出现的行标记 is_deleted=1 */
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
List<AggPatrolTask> list(@Param("stationCode") String stationCode, @Param("limit") int limit);
long count(@Param("stationCode") String stationCode);
List<AggPatrolTask> selectTaskList(@Param("stationCode") String stationCode,
@Param("taskName") String taskName);
List<AggPatrolTask> selectEnabledTasks(@Param("stationCode") String stationCode);
}

+ 46
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggPatrolTaskStatusMapper.java View File

@ -0,0 +1,46 @@
package com.inspect.agg.mapper;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggPatrolTaskStatus;
@Mapper
public interface AggPatrolTaskStatusMapper {
int upsert(AggPatrolTaskStatus e);
int markDeletedExcept(@Param("stationCode") String stationCode, @Param("ids") List<Long> ids);
List<AggPatrolTaskStatus> list(@Param("stationCode") String stationCode,
@Param("taskState") String taskState,
@Param("limit") int limit);
long count(@Param("stationCode") String stationCode, @Param("taskState") String taskState);
List<AggPatrolTaskStatus> selectTaskStatusList(@Param("stationCode") String stationCode,
@Param("taskName") String taskName,
@Param("beginTime") String beginTime,
@Param("endTime") String endTime);
List<Map<String, Object>> countMonthlyStatsByYear(@Param("stationCode") String stationCode,
@Param("year") Integer year);
List<AggPatrolTaskStatus> selectDailyStatsByYearAndMonth(@Param("stationCode") String stationCode,
@Param("year") Integer year,
@Param("month") Integer month);
List<AggPatrolTaskStatus> selectDailyStatsByYearAndMonthAndDay(@Param("stationCode") String stationCode,
@Param("year") Integer year,
@Param("month") Integer month,
@Param("day") Integer day);
Map<String, Object> selectInspectionTaskStats(@Param("stationCode") String stationCode,
@Param("pointTypeCode") String pointTypeCode,
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

+ 22
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggResultAnalysisMapper.java View File

@ -0,0 +1,22 @@
package com.inspect.agg.mapper;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggResultAnalysis;
@Mapper
public interface AggResultAnalysisMapper {
int upsert(AggResultAnalysis e);
List<Map<String, Object>> selectDefectStats(@Param("stationCode") String stationCode,
@Param("pointTypeCode") String pointTypeCode,
@Param("dateType") String dateType,
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

+ 24
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggStationMapper.java View File

@ -0,0 +1,24 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.inspect.agg.entity.AggStation;
/**
* 站点配置 Mapper:custom SQL 在 XML,无 QueryWrapper。
*/
@Mapper
public interface AggStationMapper {
List<AggStation> selectEnabled();
List<AggStation> selectAllOrdered();
AggStation selectByCode(@Param("stationCode") String stationCode);
List<AggStation> selectByEnabled(@Param("enabled") Integer enabled);
}

+ 14
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/AggSyncLogMapper.java View File

@ -0,0 +1,14 @@
package com.inspect.agg.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.inspect.agg.entity.AggSyncLog;
/**
* 同步日志 Mapper:SQL 在 XML(AggSyncLogMapper.xml),纯 MyBatis。
*/
@Mapper
public interface AggSyncLogMapper {
int insert(AggSyncLog log);
}

+ 12
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/mapper/BasedataAlgSubtypeMapper.java View File

@ -0,0 +1,12 @@
package com.inspect.agg.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.inspect.agg.entity.AggAlgSubtype;
@Mapper
public interface BasedataAlgSubtypeMapper {
List<AggAlgSubtype> selectEnabledList();
}

+ 61
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/scheduler/BaseSyncScheduler.java View File

@ -0,0 +1,61 @@
package com.inspect.agg.scheduler;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.config.SyncDefaults;
import com.inspect.agg.service.AreaSyncService;
import com.inspect.agg.service.ChannelSyncService;
import com.inspect.agg.service.DevicetypeSyncService;
import com.inspect.agg.service.DeviceSyncService;
import com.inspect.agg.service.EqpbookSyncService;
import com.inspect.agg.service.InspectionReportSyncService;
import com.inspect.agg.service.PointSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 基础数据(区域/设备/点位/台账/通道/设备类型)轮询调度:定时周期执行 + 可选启动时先同步一次。
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class BaseSyncScheduler implements ApplicationRunner {
private final AreaSyncService areaSyncService;
private final DeviceSyncService deviceSyncService;
private final PointSyncService pointSyncService;
private final EqpbookSyncService eqpbookSyncService;
private final ChannelSyncService channelSyncService;
private final DevicetypeSyncService devicetypeSyncService;
private final InspectionReportSyncService inspectionReportSyncService;
private final AggProperties props;
@Scheduled(initialDelayString = SyncDefaults.BASE_INTERVAL_MS,
fixedDelayString = SyncDefaults.BASE_INTERVAL_MS)
public void scheduledBaseSync() {
syncAll();
}
@Override
public void run(ApplicationArguments args) {
if (props.getSync().getBase().isRunOnStart()) {
log.info("run-on-start enabled, start base-data sync once");
syncAll();
}
}
private void syncAll() {
areaSyncService.syncAllStations();
deviceSyncService.syncAllStations();
pointSyncService.syncAllStations();
eqpbookSyncService.syncAllStations();
channelSyncService.syncAllStations();
devicetypeSyncService.syncAllStations();
inspectionReportSyncService.syncAllStations();
}
}

+ 28
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/scheduler/DefectRecordSyncScheduler.java View File

@ -0,0 +1,28 @@
package com.inspect.agg.scheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.inspect.agg.config.SyncDefaults;
import com.inspect.agg.service.DefectRecordSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@RequiredArgsConstructor
public class DefectRecordSyncScheduler {
private final DefectRecordSyncService defectRecordSyncService;
@Scheduled(initialDelay = 120_000L, fixedDelayString = SyncDefaults.BASE_INTERVAL_MS)
public void syncRecent() {
defectRecordSyncService.syncRecentAllStations();
}
@Scheduled(cron = "0 10 3 * * ?")
public void syncFull() {
defectRecordSyncService.syncFullAllStations();
}
}

+ 53
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/scheduler/DynamicSyncScheduler.java View File

@ -0,0 +1,53 @@
package com.inspect.agg.scheduler;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.config.SyncDefaults;
import com.inspect.agg.service.PatrolTaskSyncService;
import com.inspect.agg.service.TaskExecSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 动态数据调度:
* - 任务执行 ACTIVE:interval-ms(默认 60s)拉执行中子集
* - FULL:full-interval-ms(默认 5min)任务执行 + 任务方案全量对账
* 站点间并行(服务内 parallelStream)。
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicSyncScheduler implements ApplicationRunner {
private final TaskExecSyncService taskExecSyncService;
private final PatrolTaskSyncService patrolTaskSyncService;
private final AggProperties props;
@Scheduled(initialDelayString = SyncDefaults.DYNAMIC_INTERVAL_MS,
fixedDelayString = SyncDefaults.DYNAMIC_INTERVAL_MS)
public void scheduledActive() {
taskExecSyncService.syncActiveAllStations();
}
@Scheduled(initialDelayString = SyncDefaults.DYNAMIC_FULL_INTERVAL_MS,
fixedDelayString = SyncDefaults.DYNAMIC_FULL_INTERVAL_MS)
public void scheduledFull() {
taskExecSyncService.syncFullAllStations();
patrolTaskSyncService.syncFullAllStations();
}
@Override
public void run(ApplicationArguments args) {
if (props.getSync().getDynamic().isRunOnStart()) {
log.info("dynamic run-on-start enabled, run ACTIVE+FULL once");
taskExecSyncService.syncActiveAllStations();
taskExecSyncService.syncFullAllStations();
patrolTaskSyncService.syncFullAllStations();
}
}
}

+ 33
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/scheduler/ResultAnalysisSyncScheduler.java View File

@ -0,0 +1,33 @@
package com.inspect.agg.scheduler;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.service.AggAnalysisDefectMonthSyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@RequiredArgsConstructor
public class ResultAnalysisSyncScheduler implements ApplicationRunner {
private final AggAnalysisDefectMonthSyncService syncService;
private final AggProperties props;
@Scheduled(cron = "0 30 2 * * ?")
public void syncLast12Months() {
syncService.syncLast12MonthsAllStations();
}
@Override
public void run(ApplicationArguments args) {
if (props.getSync().getAnalysisSummary().isRunOnStart()) {
syncService.syncLast12MonthsAllStations();
}
}
}

+ 100
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/AggAnalysisDefectMonthSyncService.java View File

@ -0,0 +1,100 @@
package com.inspect.agg.service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.entity.AggAnalysisDefectMonth;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggAnalysisDefectMonthMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class AggAnalysisDefectMonthSyncService {
private static final DateTimeFormatter MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM");
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggAnalysisDefectMonthMapper defectMonthMapper;
private final SyncLogWriter syncLogWriter;
public void syncLast12MonthsAllStations() {
stationRegistry.listEnabled().parallelStream().forEach(this::syncLast12Months);
}
public void syncLast12Months(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
String message = null;
YearMonth current = YearMonth.now();
YearMonth first = current.minusMonths(11);
LocalDate begin = first.atDay(1);
LocalDate endExclusive = current.plusMonths(1).atDay(1);
try {
JsonNode root = stationApiClient.fetchAggDefectMonth(
station, first.format(MONTH_FORMAT), current.format(MONTH_FORMAT));
if (root.path("code").asInt() != 200) {
throw new IllegalStateException(root.path("msg").asText(root.toString()));
}
JsonNode data = root.path("data");
if (!data.isArray()) {
throw new IllegalStateException("station response data is not array: " + root);
}
defectMonthMapper.deleteRange(station.getStationCode(), begin, endExclusive);
for (JsonNode row : data) {
AggAnalysisDefectMonth item = map(row, station);
if (item == null) {
continue;
}
defectMonthMapper.upsert(item);
upserted++;
}
message = "rows=" + data.size() + ", upserted=" + upserted;
log.info("station={} analysis month summary ok: {}", station.getStationCode(), message);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} analysis month summary failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "analysis-month", "FULL", status,
upserted, 0, message, started, LocalDateTime.now());
}
private AggAnalysisDefectMonth map(JsonNode row, AggStation station) {
String statMonth = Rows.text(row, "statMonth");
if (!StringUtils.hasText(statMonth)) {
return null;
}
AggAnalysisDefectMonth item = new AggAnalysisDefectMonth();
item.setStationCode(station.getStationCode());
item.setStatMonth(YearMonth.parse(statMonth.trim(), MONTH_FORMAT).atDay(1));
String pointType = Rows.text(row, "pointType");
item.setPointType(StringUtils.hasText(pointType) ? pointType.trim() : "");
item.setMinorCount(value(row, "minorCount"));
item.setGeneralCount(value(row, "generalCount"));
item.setSeriousCount(value(row, "seriousCount"));
item.setUrgentCount(value(row, "urgentCount"));
item.setNormalCount(value(row, "normalCount"));
item.setSyncedAt(LocalDateTime.now());
return item;
}
private Integer value(JsonNode row, String key) {
Integer value = Rows.intVal(row, key);
return value == null ? 0 : value;
}
}

+ 144
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/AggDeviceTreeService.java View File

@ -0,0 +1,144 @@
package com.inspect.agg.service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggBasedataArea;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggBasedataAreaMapper;
import com.inspect.agg.station.StationApiClient;
import lombok.RequiredArgsConstructor;
/**
* 关系树数据尚未展开为汇聚表,按站点读取关系树并统一补充 stationCode。
*/
@Service
@RequiredArgsConstructor
public class AggDeviceTreeService {
private static final String TREE_PATH =
"basedata/prodevmnt/device/treeAreaDeviceSelectFilterByPreset/relation";
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggProperties props;
private final AggBasedataAreaMapper areaMapper;
public List<JsonNode> loadRelationTree(String stationCode, Map<String, String> params) {
List<AggStation> stations;
if (StringUtils.hasText(stationCode)) {
stations = Collections.singletonList(stationRegistry.requireByCode(stationCode));
} else {
stations = stationRegistry.listEnabled();
}
boolean aggregate = !StringUtils.hasText(stationCode);
List<JsonNode> result = new ArrayList<>();
int pageSize = props.getSync().getBase().getPageSize();
for (AggStation station : stations) {
Map<Long, Integer> areaOrder = areaOrderMap(station.getStationCode());
List<JsonNode> roots = stationApiClient.fetchAllRows(
station, TREE_PATH, pageSize, 20, params, "dataId");
for (JsonNode root : roots) {
ObjectNode copy = (ObjectNode) root.deepCopy();
normalizeNode(copy, station.getStationCode(),
station.getStationName() != null ? station.getStationName() : station.getStationCode(),
aggregate);
sortAreaChildren(copy, areaOrder);
result.add(copy);
}
}
return result;
}
private Map<Long, Integer> areaOrderMap(String stationCode) {
Map<Long, Integer> orderMap = new HashMap<>();
for (AggBasedataArea area : areaMapper.listForTree(stationCode)) {
if (area.getAreaId() != null) {
orderMap.put(area.getAreaId(), area.getOrderNum() == null ? Integer.MAX_VALUE : area.getOrderNum());
}
}
return orderMap;
}
private static void sortAreaChildren(JsonNode node, Map<Long, Integer> areaOrder) {
if (node == null || !node.isObject()) {
return;
}
JsonNode children = node.get("children");
if (children == null || !children.isArray()) {
return;
}
List<JsonNode> sorted = new ArrayList<>();
for (JsonNode child : children) {
sorted.add(child);
}
sorted.sort(Comparator
.comparingInt((JsonNode child) -> isAreaNode(child) ? 0 : 1)
.thenComparingInt(child -> isAreaNode(child) ? areaOrderValue(child, areaOrder) : 0));
ArrayNode array = (ArrayNode) children;
array.removeAll();
for (JsonNode child : sorted) {
array.add(child);
}
for (JsonNode child : array) {
sortAreaChildren(child, areaOrder);
}
}
private static boolean isAreaNode(JsonNode node) {
JsonNode flag = node.get("areaFlag");
return flag != null && "Yes".equals(flag.asText());
}
private static int areaOrderValue(JsonNode node, Map<Long, Integer> areaOrder) {
JsonNode areaId = node.get("areaId");
if (areaId == null || areaId.isNull()) {
return Integer.MAX_VALUE;
}
Integer order = areaOrder.get(areaId.asLong());
return order == null ? Integer.MAX_VALUE : order;
}
private static void normalizeNode(ObjectNode node, String stationCode, String stationName,
boolean prefixIds) {
if (node == null || !node.isObject()) {
return;
}
node.put("stationCode", stationCode);
node.put("stationName", stationName != null ? stationName : stationCode);
if (prefixIds) {
String dataId = text(node, "dataId");
if (StringUtils.hasText(dataId)) {
node.put("dataId", stationCode + ":" + dataId);
}
String parentId = text(node, "parentId");
if (StringUtils.hasText(parentId)) {
node.put("parentId", stationCode + ":" + parentId);
}
}
JsonNode children = node.get("children");
if (children != null && children.isArray()) {
for (JsonNode child : children) {
normalizeNode((ObjectNode) child, stationCode, stationName, prefixIds);
}
}
}
private static String text(JsonNode node, String key) {
JsonNode value = node.get(key);
return value == null || value.isNull() ? null : value.asText();
}
}

+ 295
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/AggStatisticService.java View File

@ -0,0 +1,295 @@
package com.inspect.agg.service;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.inspect.agg.entity.AggAnalysisDefectMonth;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.enums.AlgCategory;
import com.inspect.agg.mapper.AggAnalysisDefectMonthMapper;
import com.inspect.agg.mapper.AggStationMapper;
import com.inspect.agg.mapper.AggPatrolTaskStatusMapper;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class AggStatisticService {
private static final DateTimeFormatter MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM");
private static final String MONTH = "month";
private static final String QUARTER = "quarter";
private static final String YEAR = "year";
/** 统计窗口下限:最早只能查 2026-10(季度下限即 2026-Q4 的首月)。 */
private static final YearMonth MIN_STAT_MONTH = YearMonth.of(2026, 10);
private static final YearMonth MIN_STAT_QUARTER_START = YearMonth.of(2026, 10);
private final AggAnalysisDefectMonthMapper defectMonthMapper;
private final AggPatrolTaskStatusMapper taskStatusMapper;
private final AggStationMapper stationMapper;
public List<Map<String, Object>> defectStatistic(String stationCode, String algCategory,
String dateType) {
List<AlgCategory> categories = parseCategories(algCategory);
String normalizedType = normalizeDateType(dateType);
YearMonth current = YearMonth.now();
// 固定长度窗口:月 12 个、季 4 个、年 3 个;右端对齐当前周期,
// 若左端早于统计下限(2026-10),整体右移,窗口长度不变。
YearMonth windowStart;
int windowMonths;
if (YEAR.equals(normalizedType)) {
int startYear = Math.max(current.getYear() - 2, MIN_STAT_MONTH.getYear());
windowStart = YearMonth.of(startYear, 1);
windowMonths = 36;
} else if (QUARTER.equals(normalizedType)) {
YearMonth start = quarterStart(current).minusMonths(9);
windowStart = start.isBefore(MIN_STAT_QUARTER_START) ? MIN_STAT_QUARTER_START : start;
windowMonths = 12;
} else {
YearMonth start = current.minusMonths(11);
windowStart = start.isBefore(MIN_STAT_MONTH) ? MIN_STAT_MONTH : start;
windowMonths = 12;
}
LocalDate begin = windowStart.atDay(1);
LocalDate endExclusive = windowStart.plusMonths(windowMonths).atDay(1);
List<AggAnalysisDefectMonth> rows = defectMonthMapper.selectRange(stationCode, begin, endExclusive);
Map<YearMonth, Counts> monthlyMap = toMonthlyMap(rows, categories);
String stationName = stationDisplayName(stationCode);
List<Map<String, Object>> result = new ArrayList<>();
if (YEAR.equals(normalizedType)) {
int startYear = windowStart.getYear();
for (int i = 0; i < 3; i++) {
int year = startYear + i;
Counts counts = new Counts();
for (int monthOffset = 0; monthOffset < 12; monthOffset++) {
counts.add(monthlyMap.get(YearMonth.of(year, 1).plusMonths(monthOffset)));
}
result.add(toPeriodResult(counts, String.valueOf(year),
normalizedType, algCategory, stationCode, stationName));
}
return result;
}
if (QUARTER.equals(normalizedType)) {
for (int i = 0; i < 4; i++) {
YearMonth quarter = windowStart.plusMonths(i * 3L);
Counts counts = new Counts();
for (int monthOffset = 0; monthOffset < 3; monthOffset++) {
counts.add(monthlyMap.get(quarter.plusMonths(monthOffset)));
}
result.add(toPeriodResult(counts,
quarter.getYear() + "-Q" + ((quarter.getMonthValue() - 1) / 3 + 1),
normalizedType, algCategory, stationCode, stationName));
}
return result;
}
for (int i = 0; i < 12; i++) {
YearMonth month = windowStart.plusMonths(i);
Counts counts = monthlyMap.getOrDefault(month, new Counts());
result.add(toPeriodResult(counts, month.format(MONTH_FORMAT), normalizedType, algCategory,
stationCode, stationName));
}
return result;
}
public List<Map<String, Object>> inspectionStatistic(String stationCode, String algCategory) {
AlgCategory category = AlgCategory.fromLabel(algCategory);
String stationName = stationDisplayName(stationCode);
if (category != null) {
return java.util.Collections.singletonList(inspectionByCategory(stationCode, stationName, category));
}
List<Map<String, Object>> result = new ArrayList<>();
for (AlgCategory item : AlgCategory.values()) {
result.add(inspectionByCategory(stationCode, stationName, item));
}
return result;
}
private Map<String, Object> inspectionByCategory(String stationCode, String stationName,
AlgCategory category) {
YearMonth current = YearMonth.now();
LocalDate begin = current.atDay(1);
LocalDate endExclusive = current.plusMonths(1).atDay(1);
Map<String, Object> taskStats = taskStatusMapper.selectInspectionTaskStats(
stationCode, category.getCode(), begin.atStartOfDay(), endExclusive.atStartOfDay());
List<AggAnalysisDefectMonth> rows = defectMonthMapper.selectRange(stationCode, begin, endExclusive);
Counts defectStats = new Counts();
for (AggAnalysisDefectMonth row : rows) {
if (category.getCode().equals(emptyIfNull(row.getPointType()))) {
defectStats.add(row);
}
}
return toInspectionResult(category.getLabel(), taskStats, defectStats, stationCode, stationName);
}
private Map<YearMonth, Counts> toMonthlyMap(List<AggAnalysisDefectMonth> rows,
List<AlgCategory> categories) {
Set<String> codes = new HashSet<>();
for (AlgCategory category : categories) {
codes.add(category.getCode());
}
Map<YearMonth, Counts> result = new LinkedHashMap<>();
for (AggAnalysisDefectMonth row : rows) {
String type = emptyIfNull(row.getPointType());
boolean matched = categories.isEmpty() ? "".equals(type) : codes.contains(type);
if (!matched) {
continue;
}
YearMonth month = YearMonth.from(row.getStatMonth());
result.computeIfAbsent(month, key -> new Counts()).add(row);
}
return result;
}
private Map<String, Object> toPeriodResult(Counts counts, String period,
String dateType, String algCategory,
String stationCode, String stationName) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("date", period);
item.put("dateType", dateType);
if (StringUtils.hasText(algCategory)) {
item.put("algCategory", algCategory);
}
if (StringUtils.hasText(stationCode)) {
item.put("stationCode", stationCode);
item.put("stationName", stationName != null ? stationName : stationCode);
}
item.put("generalCount", counts.general);
item.put("seriousCount", counts.serious);
item.put("urgentCount", counts.urgent);
item.put("normalCount", counts.normal);
item.put("defectCount", defectCount(counts));
return item;
}
private Map<String, Object> toInspectionResult(String label, Map<String, Object> taskStats,
Counts defectStats,
String stationCode, String stationName) {
Map<String, Object> result = new LinkedHashMap<>();
result.put("algCategory", label);
if (StringUtils.hasText(stationCode)) {
result.put("stationCode", stationCode);
result.put("stationName", stationName != null ? stationName : stationCode);
}
result.put("inspectedTaskCount", number(taskStats, "inspected_task_count"));
result.put("completedTaskCount", number(taskStats, "completed_task_count"));
result.put("defectCount", defectCount(defectStats));
result.put("generalCount", defectStats.general);
result.put("seriousCount", defectStats.serious);
result.put("urgentCount", defectStats.urgent);
return result;
}
/**
* 缺陷总数只统计一般及以上缺陷,轻微缺陷不计入。
*/
private long defectCount(Counts counts) {
return (long) counts.general + counts.serious + counts.urgent;
}
private String stationDisplayName(String stationCode) {
if (!StringUtils.hasText(stationCode)) {
return null;
}
AggStation station = stationMapper.selectByCode(stationCode);
if (station != null && StringUtils.hasText(station.getStationName())) {
return station.getStationName();
}
return stationCode;
}
private static List<AlgCategory> parseCategories(String algCategory) {
List<AlgCategory> categories = new ArrayList<>();
Set<String> codes = new HashSet<>();
if (!StringUtils.hasText(algCategory)) {
return categories;
}
for (String part : algCategory.split(",")) {
String label = part.trim();
if (label.isEmpty()) {
continue;
}
AlgCategory category = AlgCategory.fromLabel(label);
if (codes.add(category.getCode())) {
categories.add(category);
}
}
return categories;
}
private static String normalizeDateType(String dateType) {
String value = dateType == null ? "" : dateType.trim();
if (QUARTER.equalsIgnoreCase(value)) {
return QUARTER;
}
if (YEAR.equalsIgnoreCase(value)) {
return YEAR;
}
return MONTH;
}
private static YearMonth quarterStart(YearMonth month) {
int quarterMonth = ((month.getMonthValue() - 1) / 3) * 3 + 1;
return YearMonth.of(month.getYear(), quarterMonth);
}
private static long number(Map<String, Object> row, String key) {
if (row == null) {
return 0L;
}
Object value = row.get(key);
if (value == null) {
value = row.get(key.toUpperCase());
}
return value instanceof Number ? ((Number) value).longValue() : 0L;
}
private static String emptyIfNull(String value) {
return value == null ? "" : value;
}
private static class Counts {
private int general;
private int serious;
private int urgent;
private int normal;
private void add(AggAnalysisDefectMonth row) {
if (row == null) {
return;
}
general += nz(row.getGeneralCount());
serious += nz(row.getSeriousCount());
urgent += nz(row.getUrgentCount());
normal += nz(row.getNormalCount());
}
private void add(Counts other) {
if (other == null) {
return;
}
general += other.general;
serious += other.serious;
urgent += other.urgent;
normal += other.normal;
}
private static int nz(Integer value) {
return value == null ? 0 : value;
}
}
}

+ 99
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/AreaSyncService.java View File

@ -0,0 +1,99 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggBasedataArea;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggBasedataAreaMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 区域同步服务(基础数据全量:源 basedata_area)。站点配置来自 DB(StationRegistry)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AreaSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggBasedataAreaMapper aggAreaMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncAllStations() {
for (AggStation station : stationRegistry.listEnabled()) {
syncStation(station);
}
}
public void syncStation(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllAreas(station,
props.getSync().getBase().getPageSize(), 500);
List<Long> ids = new ArrayList<>();
for (JsonNode row : rows) {
AggBasedataArea area = map(row, station);
if (area == null) {
continue;
}
aggAreaMapper.upsert(area);
if (area.getAreaId() != null) {
ids.add(area.getAreaId());
}
upserted++;
}
deletedMarked = aggAreaMapper.markDeletedExcept(station.getStationCode(), ids);
message = "rows=" + rows.size();
log.info("station={} area sync ok: upserted={}, deletedMarked={}", station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} area sync failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "area", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
private AggBasedataArea map(JsonNode r, AggStation station) {
Long areaId = Rows.longVal(r, "areaId", "area_id");
if (areaId == null) {
return null;
}
AggBasedataArea a = new AggBasedataArea();
a.setStationCode(station.getStationCode());
a.setAreaId(areaId);
Long parentId = Rows.longVal(r, "parentId", "parent_id", "pid");
a.setParentId(parentId == null ? 0L : parentId);
a.setAncestors(Rows.text(r, "ancestors"));
a.setAreaName(Rows.text(r, "areaName", "area_name"));
a.setAreaCode(Rows.text(r, "areaCode", "area_code"));
a.setAreaAddr(Rows.text(r, "areaAddr", "area_addr"));
a.setAreaType(Rows.text(r, "areaType", "area_type"));
a.setDeviceTypeCode(Rows.text(r, "deviceTypeCode", "device_type_code"));
Integer orderNum = Rows.intVal(r, "orderNum", "order_num");
a.setOrderNum(orderNum == null ? 0 : orderNum);
a.setStatus(Rows.text(r, "status"));
a.setRemark(Rows.text(r, "remark"));
a.setUpdateTime(Rows.date(r, "updateTime", "update_time"));
a.setSyncedAt(LocalDateTime.now());
a.setIsDeleted(0);
return a;
}
}

+ 102
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/ChannelSyncService.java View File

@ -0,0 +1,102 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggBasedataEqpbookChannel;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggBasedataEqpbookChannelMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 设备通道同步服务(基础数据全量:源 basedata_eqpbook_channel)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ChannelSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggBasedataEqpbookChannelMapper aggChannelMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncAllStations() {
for (AggStation station : stationRegistry.listEnabled()) {
syncStation(station);
}
}
public void syncStation(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllChannels(station,
props.getSync().getBase().getPageSize(), 100);
List<Long> ids = new ArrayList<>();
for (JsonNode row : rows) {
AggBasedataEqpbookChannel c = map(row, station);
if (c == null) {
continue;
}
aggChannelMapper.upsert(c);
if (c.getChannelId() != null) {
ids.add(c.getChannelId());
}
upserted++;
}
deletedMarked = aggChannelMapper.markDeletedExcept(station.getStationCode(), ids);
message = "rows=" + rows.size();
log.info("station={} channel sync ok: upserted={}, deletedMarked={}", station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} channel sync failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "channel", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
private AggBasedataEqpbookChannel map(JsonNode r, AggStation station) {
Long id = Rows.longVal(r, "channelId");
if (id == null) {
return null;
}
AggBasedataEqpbookChannel c = new AggBasedataEqpbookChannel();
c.setStationCode(station.getStationCode());
c.setChannelId(id);
c.setChannelCode(Rows.text(r, "channelCode"));
c.setChannelName(Rows.text(r, "channelName"));
c.setChannelType(Rows.text(r, "channelType"));
c.setOnlineState(Rows.text(r, "onlineState"));
c.setChannelContent(Rows.text(r, "channelContent"));
c.setHost(Rows.text(r, "host"));
c.setPort(Rows.text(r, "port"));
c.setAreaId(Rows.longVal(r, "areaId"));
c.setAreaName(Rows.text(r, "areaName"));
c.setEqpbookId(Rows.longVal(r, "eqpbookId"));
c.setPatrolDeviceCode(Rows.text(r, "patroldeviceCode", "patrolDeviceCode"));
c.setUpSystemDeviceCode(Rows.text(r, "upSystemDeviceCode"));
c.setDeviceSource(Rows.text(r, "deviceSource"));
c.setCapabilities(Rows.jsonText(r, "capabilities"));
c.setUseMode(Rows.text(r, "useMode", "use_mode"));
c.setUpdateTime(Rows.date(r, "updateTime"));
c.setSyncedAt(LocalDateTime.now());
c.setIsDeleted(0);
return c;
}
}

+ 185
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/DefectRecordSyncService.java View File

@ -0,0 +1,185 @@
package com.inspect.agg.service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggDefectRecord;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggDefectRecordMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* Syncs merged defect-record rows from station-side /patrol/analysis/defect/list.
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DefectRecordSyncService {
private static final String PATH = "patrol/analysis/defect/list";
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggDefectRecordMapper defectRecordMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncFullAllStations() {
stationRegistry.listEnabled().parallelStream().forEach(this::syncFull);
}
public void syncRecentAllStations() {
stationRegistry.listEnabled().parallelStream().forEach(this::syncRecent);
}
public void syncFull(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<AggDefectRecord> existing = defectRecordMapper.selectActiveKeys(station.getStationCode());
Map<String, Long> existingIds = existing.stream()
.collect(Collectors.toMap(this::naturalKey, AggDefectRecord::getId, (oldValue, ignored) -> oldValue));
List<JsonNode> rows = stationApiClient.fetchAllRows(station, PATH,
props.getSync().getBase().getPageSize(), 1000, "lineId");
Set<String> presentKeys = new HashSet<>();
for (JsonNode row : rows) {
AggDefectRecord record = map(row, station);
if (record == null) {
continue;
}
defectRecordMapper.upsert(record);
presentKeys.add(naturalKey(record));
upserted++;
}
List<Long> missingIds = existingIds.entrySet().stream()
.filter(entry -> !presentKeys.contains(entry.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
if (!missingIds.isEmpty()) {
deletedMarked = defectRecordMapper.markDeletedByIds(station.getStationCode(), missingIds);
}
message = "rows=" + rows.size() + ", deletedMarked=" + deletedMarked;
log.info("station={} defect record FULL ok: upserted={}, deletedMarked={}",
station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} defect record FULL failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "defect", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
public void syncRecent(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
String message = null;
try {
Map<String, String> query = new HashMap<>();
query.put("beginTime", LocalDate.now().minusDays(7).toString());
query.put("endTime", LocalDate.now().toString());
List<JsonNode> rows = stationApiClient.fetchAllRows(station, PATH,
props.getSync().getBase().getPageSize(), 200, query, "lineId");
for (JsonNode row : rows) {
AggDefectRecord record = map(row, station);
if (record != null) {
defectRecordMapper.upsert(record);
upserted++;
}
}
message = "rows=" + rows.size();
log.info("station={} defect record RECENT ok: upserted={}", station.getStationCode(), upserted);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} defect record RECENT failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "defect", "RECENT", status, upserted, 0,
message, started, LocalDateTime.now());
}
private AggDefectRecord map(JsonNode r, AggStation station) {
String taskPatrolId = text(r, "taskPatrolId");
String objectId = text(r, "objectId");
String algType = text(r, "algType");
if (isBlank(taskPatrolId) || isBlank(objectId) || isBlank(algType)) {
return null;
}
AggDefectRecord e = new AggDefectRecord();
e.setStationCode(station.getStationCode());
e.setDeviceName(text(r, "deviceName"));
e.setChannelName(text(r, "channelName"));
e.setChannelCode(text(r, "channelCode"));
e.setDeviceSource(text(r, "deviceSource"));
e.setPointName(text(r, "pointName"));
e.setPointId(text(r, "pointId"));
e.setWarnStatus(text(r, "warnStatus"));
e.setAlgName(text(r, "algName"));
e.setResValue(text(r, "resValue"));
e.setImg(text(r, "img"));
e.setFilterDesc(text(r, "filterDesc"));
e.setBrightDesc(text(r, "brightDesc"));
e.setImgAnalyse(text(r, "imgAnalyse"));
e.setBrightImgAnalyse(text(r, "brightImgAnalyse"));
e.setDesc(text(r, "desc"));
e.setLineId(text(r, "lineId"));
e.setPatrolTime(Rows.date(r, "patrolTime"));
e.setPointStatus(text(r, "pointStatus"));
e.setImgType(text(r, "imgType"));
e.setDataType(text(r, "dataType"));
e.setFilter(text(r, "filter"));
e.setRequestId(text(r, "requestId"));
e.setResStatus(text(r, "resStatus"));
e.setBrightResStatus(text(r, "brightResStatus"));
e.setAreaName(text(r, "areaName"));
e.setAreaId(text(r, "areaId"));
e.setAlgType(algType);
e.setObjectId(objectId);
e.setImageNormalUrlPath(text(r, "imageNormalUrlPath"));
e.setFilePath(text(r, "filePath"));
e.setSuggestion(text(r, "suggestion"));
e.setOriginalValue(text(r, "originalValue"));
e.setTaskPatrolId(taskPatrolId);
e.setTaskName(text(r, "taskName"));
Integer defectLevel = Rows.intVal(r, "defectLevel");
e.setDefectLevel(defectLevel == null ? 0 : defectLevel);
e.setSyncedAt(LocalDateTime.now());
e.setIsDeleted(0);
return e;
}
private String naturalKey(AggDefectRecord r) {
return r.getStationCode() + "|" + r.getTaskPatrolId() + "|" + r.getObjectId() + "|" + r.getAlgType();
}
private static String text(JsonNode node, String key) {
JsonNode value = node.get(key);
if (value == null || value.isNull()) {
return null;
}
return value.asText();
}
private static boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
}

+ 103
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/DeviceSyncService.java View File

@ -0,0 +1,103 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggBasedataDevice;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggBasedataDeviceMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 设备同步服务(基础数据全量:源 basedata_device)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggBasedataDeviceMapper aggDeviceMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncAllStations() {
for (AggStation station : stationRegistry.listEnabled()) {
syncStation(station);
}
}
public void syncStation(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllDevices(station,
props.getSync().getBase().getPageSize(), 500);
List<Long> ids = new ArrayList<>();
for (JsonNode row : rows) {
AggBasedataDevice d = map(row, station);
if (d == null) {
continue;
}
aggDeviceMapper.upsert(d);
if (d.getDeviceId() != null) {
ids.add(d.getDeviceId());
}
upserted++;
}
deletedMarked = aggDeviceMapper.markDeletedExcept(station.getStationCode(), ids);
message = "rows=" + rows.size();
log.info("station={} device sync ok: upserted={}, deletedMarked={}", station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} device sync failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "device", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
private AggBasedataDevice map(JsonNode r, AggStation station) {
Long deviceId = Rows.longVal(r, "deviceId");
if (deviceId == null) {
return null;
}
AggBasedataDevice d = new AggBasedataDevice();
d.setStationCode(station.getStationCode());
d.setDeviceId(deviceId);
Long parentId = Rows.longVal(r, "parentId");
d.setParentId(parentId == null ? 0L : parentId);
d.setAncestors(Rows.text(r, "ancestors"));
d.setAreaId(Rows.longVal(r, "areaId"));
d.setAreaName(Rows.text(r, "areaName"));
d.setDeviceCode(Rows.text(r, "deviceCode"));
d.setDeviceName(Rows.text(r, "deviceName"));
d.setDeviceTypeName(Rows.text(r, "deviceTypeName"));
d.setDeviceModel(Rows.text(r, "deviceModel"));
d.setManufacturer(Rows.text(r, "manufacturer"));
d.setPowerMainEquFlag(Rows.text(r, "powerMainEquFlag"));
d.setStationNo(Rows.text(r, "stationNo"));
d.setMonitorIndexNo(Rows.text(r, "monitorIndexNo"));
d.setStatus(Rows.text(r, "status"));
Integer orderNum = Rows.intVal(r, "orderNum");
d.setOrderNum(orderNum == null ? 0 : orderNum);
d.setUpdateTime(Rows.date(r, "updateTime"));
d.setSyncedAt(LocalDateTime.now());
d.setIsDeleted(0);
return d;
}
}

+ 102
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/DevicetypeSyncService.java View File

@ -0,0 +1,102 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggBasedataDevicetype;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggBasedataDevicetypeMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 设备类型同步服务(基础数据全量:源 basedata_devicetype)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DevicetypeSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggBasedataDevicetypeMapper aggDevicetypeMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncAllStations() {
for (AggStation station : stationRegistry.listEnabled()) {
syncStation(station);
}
}
public void syncStation(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllDeviceTypes(station,
props.getSync().getBase().getPageSize(), 100);
List<Long> ids = new ArrayList<>();
for (JsonNode row : rows) {
AggBasedataDevicetype d = map(row, station);
if (d == null) {
continue;
}
aggDevicetypeMapper.upsert(d);
if (d.getDeviceTypeId() != null) {
ids.add(d.getDeviceTypeId());
}
upserted++;
}
deletedMarked = aggDevicetypeMapper.markDeletedExcept(station.getStationCode(), ids);
message = "rows=" + rows.size();
log.info("station={} devicetype sync ok: upserted={}, deletedMarked={}", station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} devicetype sync failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "devicetype", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
private AggBasedataDevicetype map(JsonNode r, AggStation station) {
Long id = Rows.longVal(r, "devicetypeId");
if (id == null) {
return null;
}
AggBasedataDevicetype d = new AggBasedataDevicetype();
d.setStationCode(station.getStationCode());
d.setDeviceTypeId(id);
Long parentId = Rows.longVal(r, "parentId");
d.setParentId(parentId == null ? 0L : parentId);
Integer orderNum = Rows.intVal(r, "orderNum");
d.setOrderNum(orderNum == null ? 0 : orderNum);
d.setAncestors(Rows.text(r, "ancestors"));
d.setAncestorsAddr(Rows.text(r, "ancestorsAddr"));
d.setDeviceTypeName(Rows.text(r, "devicetypeName", "deviceTypeName"));
d.setDeviceTypeCode(Rows.text(r, "devicetypeCode", "deviceTypeCode"));
d.setPowerMainEquFlag(Rows.text(r, "powerMainEquFlag"));
d.setPhaseNum(Rows.text(r, "phaseNum"));
d.setPhase(Rows.text(r, "phase"));
d.setStatus(Rows.text(r, "status"));
d.setProfessionType(Rows.text(r, "professionType"));
Long level = Rows.longVal(r, "level");
d.setLevel(level == null ? 0L : level);
d.setUpdateTime(Rows.date(r, "updateTime"));
d.setSyncedAt(LocalDateTime.now());
d.setIsDeleted(0);
return d;
}
}

+ 103
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/EqpbookSyncService.java View File

@ -0,0 +1,103 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggBasedataEqpbook;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggBasedataEqpbookMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 设备台账同步服务(基础数据全量:源 basedata_eqpbook)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class EqpbookSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggBasedataEqpbookMapper aggEqpbookMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncAllStations() {
for (AggStation station : stationRegistry.listEnabled()) {
syncStation(station);
}
}
public void syncStation(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllEqpbooks(station,
props.getSync().getBase().getPageSize(), 100);
List<Long> ids = new ArrayList<>();
for (JsonNode row : rows) {
AggBasedataEqpbook e = map(row, station);
if (e == null) {
continue;
}
aggEqpbookMapper.upsert(e);
if (e.getEqpbookId() != null) {
ids.add(e.getEqpbookId());
}
upserted++;
}
deletedMarked = aggEqpbookMapper.markDeletedExcept(station.getStationCode(), ids);
message = "rows=" + rows.size();
log.info("station={} eqpbook sync ok: upserted={}, deletedMarked={}", station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} eqpbook sync failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "eqpbook", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
private AggBasedataEqpbook map(JsonNode r, AggStation station) {
Long id = Rows.longVal(r, "eqpbookId");
if (id == null) {
return null;
}
AggBasedataEqpbook e = new AggBasedataEqpbook();
e.setStationCode(station.getStationCode());
e.setEqpbookId(id);
e.setPatrolDeviceCode(Rows.text(r, "patroldeviceCode", "patrolDeviceCode"));
e.setPatrolDeviceName(Rows.text(r, "patroldeviceName", "patrolDeviceName"));
e.setStationName(Rows.text(r, "stationName"));
e.setAreaId(Rows.longVal(r, "areaId"));
e.setAreaName(Rows.text(r, "areaName"));
e.setDeviceSource(Rows.text(r, "deviceSource"));
e.setDeviceModel(Rows.text(r, "deviceModel"));
e.setManufacturer(Rows.text(r, "manufacturer"));
e.setDeviceType(Rows.text(r, "type"));
e.setIpAddr(Rows.text(r, "ipAddr"));
e.setPort(Rows.text(r, "port"));
e.setVideoNvrCode(Rows.text(r, "videoNvrCode"));
e.setMainSystemCode(Rows.text(r, "mainSystemCode"));
e.setUseMode(Rows.text(r, "useMode"));
e.setVideoMode(Rows.text(r, "videoMode"));
e.setUpdateTime(Rows.date(r, "updateTime"));
e.setSyncedAt(LocalDateTime.now());
e.setIsDeleted(0);
return e;
}
}

+ 280
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/EqpbookTreeService.java View File

@ -0,0 +1,280 @@
package com.inspect.agg.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.inspect.agg.entity.AggBasedataArea;
import com.inspect.agg.entity.AggBasedataEqpbook;
import com.inspect.agg.entity.AggBasedataEqpbookChannel;
import com.inspect.agg.mapper.AggBasedataAreaMapper;
import com.inspect.agg.mapper.AggBasedataEqpbookChannelMapper;
import com.inspect.agg.mapper.AggBasedataEqpbookMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 使用汇聚区域、台账和通道数据构建通道树。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class EqpbookTreeService {
private final AggBasedataAreaMapper areaMapper;
private final AggBasedataEqpbookMapper eqpbookMapper;
private final AggBasedataEqpbookChannelMapper channelMapper;
private final ObjectMapper objectMapper;
public List<Map<String, Object>> buildFilterTree(String stationCode,
String patrolDeviceName,
String onlineStatus,
String patrolDeviceTypeFlag,
String deviceSource,
String useMode) {
List<AggBasedataArea> areas = areaMapper.listForTree(stationCode);
List<AggBasedataEqpbook> eqpbooks = eqpbookMapper.list(stationCode, 0);
List<AggBasedataEqpbookChannel> channels = channelMapper.list(stationCode, 0);
Map<String, AggBasedataArea> areaIndex = new HashMap<>();
for (AggBasedataArea area : areas) {
areaIndex.put(areaKey(area.getStationCode(), area.getAreaId()), area);
}
Map<String, List<AggBasedataEqpbookChannel>> channelIndex = new HashMap<>();
for (AggBasedataEqpbookChannel channel : channels) {
if (!matchesDeviceSource(channel, deviceSource) || !matchesUseMode(channel, useMode)) {
continue;
}
channelIndex.computeIfAbsent(eqpbookKey(channel.getStationCode(), channel.getEqpbookId()),
key -> new ArrayList<>()).add(channel);
}
List<AggBasedataEqpbook> selectedEqpbooks = new ArrayList<>();
Set<String> selectedAreaKeys = new HashSet<>();
for (AggBasedataEqpbook eqpbook : eqpbooks) {
if (!matchesName(eqpbook, patrolDeviceName) || !matchesType(eqpbook, patrolDeviceTypeFlag)) {
continue;
}
List<AggBasedataEqpbookChannel> eqpbookChannels =
channelIndex.getOrDefault(eqpbookKey(eqpbook.getStationCode(), eqpbook.getEqpbookId()),
new ArrayList<AggBasedataEqpbookChannel>());
if (eqpbookChannels.isEmpty()) {
continue;
}
boolean online = false;
for (AggBasedataEqpbookChannel channel : eqpbookChannels) {
if ("1".equals(channel.getOnlineState())) {
online = true;
break;
}
}
if ("1".equals(onlineStatus) && !online) continue;
if ("0".equals(onlineStatus) && online) continue;
AggBasedataArea area = areaIndex.get(areaKey(eqpbook.getStationCode(), eqpbook.getAreaId()));
if (area == null) {
continue;
}
selectedEqpbooks.add(eqpbook);
collectAreaKeys(area, areaIndex, selectedAreaKeys);
}
Map<String, Map<String, Object>> areaNodes = new LinkedHashMap<>();
for (AggBasedataArea area : areas) {
String key = areaKey(area.getStationCode(), area.getAreaId());
if (!selectedAreaKeys.contains(key)) continue;
Map<String, Object> node = new LinkedHashMap<>();
node.put("id", key);
String parentKey = area.getParentId() == null ? null : areaKey(area.getStationCode(), area.getParentId());
node.put("parentId", selectedAreaKeys.contains(parentKey) ? parentKey : null);
node.put("label", area.getAreaName());
node.put("areaId", area.getAreaId());
node.put("areaName", area.getAreaName());
node.put("areaFlag", "Yes");
node.put("areaType", area.getAreaType());
node.put("stationCode", area.getStationCode());
node.put("stationName", area.getStationName() != null ? area.getStationName() : area.getStationCode());
node.put("children", new ArrayList<Map<String, Object>>());
areaNodes.put(key, node);
}
for (AggBasedataEqpbook eqpbook : selectedEqpbooks) {
String parentKey = areaKey(eqpbook.getStationCode(), eqpbook.getAreaId());
Map<String, Object> areaNode = areaNodes.get(parentKey);
if (areaNode == null) continue;
List<AggBasedataEqpbookChannel> eqpbookChannels =
channelIndex.get(eqpbookKey(eqpbook.getStationCode(), eqpbook.getEqpbookId()));
boolean online = false;
for (AggBasedataEqpbookChannel channel : eqpbookChannels) {
if ("1".equals(channel.getOnlineState())) {
online = true;
break;
}
}
String eqpbookNodeId = eqpbookKey(eqpbook.getStationCode(), eqpbook.getEqpbookId());
Map<String, Object> eqpbookNode = new LinkedHashMap<>();
eqpbookNode.put("id", eqpbookNodeId);
eqpbookNode.put("parentId", parentKey);
eqpbookNode.put("label", eqpbook.getPatrolDeviceName());
eqpbookNode.put("areaId", eqpbook.getAreaId());
eqpbookNode.put("areaName", eqpbook.getAreaName());
eqpbookNode.put("eqpbookId", eqpbook.getEqpbookId());
eqpbookNode.put("patroldeviceFlag", "Yes");
eqpbookNode.put("patroldeviceCode", eqpbook.getPatrolDeviceCode());
eqpbookNode.put("patroldeviceName", eqpbook.getPatrolDeviceName());
eqpbookNode.put("patroldeviceType", eqpbook.getDeviceType());
eqpbookNode.put("patroldeviceOnlineStatus", online ? "1" : "0");
eqpbookNode.put("type", eqpbook.getDeviceType());
eqpbookNode.put("stationCode", eqpbook.getStationCode());
eqpbookNode.put("stationName", eqpbook.getStationName() != null ? eqpbook.getStationName() : eqpbook.getStationCode());
eqpbookNode.put("children", new ArrayList<Map<String, Object>>());
children(areaNode).add(eqpbookNode);
for (AggBasedataEqpbookChannel channel : eqpbookChannels) {
Map<String, Object> channelNode = new LinkedHashMap<>();
channelNode.put("id", eqpbookNodeId + ":channel:" + channel.getChannelId());
channelNode.put("parentId", eqpbookNodeId);
channelNode.put("label", channel.getChannelName());
channelNode.put("areaId", eqpbook.getAreaId());
channelNode.put("areaName", eqpbook.getAreaName());
channelNode.put("patroldeviceCode", eqpbook.getPatrolDeviceCode());
channelNode.put("patroldeviceName", eqpbook.getPatrolDeviceName());
channelNode.put("patroldeviceType", eqpbook.getDeviceType());
channelNode.put("patroldeviceOnlineStatus", channel.getOnlineState());
channelNode.put("channelId", channel.getChannelId());
channelNode.put("channelCode", channel.getChannelCode());
channelNode.put("channelName", channel.getChannelName());
channelNode.put("channelFlag", "Yes");
channelNode.put("channelType", channel.getChannelType());
channelNode.put("deviceSource", channel.getDeviceSource());
channelNode.put("capabilities", parseCapabilities(channel.getCapabilities()));
channelNode.put("useMode", channel.getUseMode());
channelNode.put("host", channel.getHost());
channelNode.put("port", channel.getPort());
channelNode.put("stationCode", channel.getStationCode());
channelNode.put("stationName", channel.getStationName() != null ? channel.getStationName() : channel.getStationCode());
children(eqpbookNode).add(channelNode);
}
}
List<Map<String, Object>> roots = new ArrayList<>();
for (Map<String, Object> node : areaNodes.values()) {
if (node.get("parentId") == null) {
roots.add(node);
} else {
Map<String, Object> parent = areaNodes.get(node.get("parentId"));
if (parent != null) {
children(parent).add(node);
} else {
roots.add(node);
}
}
}
return roots;
}
private Map<String, Object> parseCapabilities(String value) {
if (!StringUtils.hasText(value)) {
return null;
}
try {
JsonNode node = objectMapper.readTree(value);
if (node == null || !node.isObject()) {
log.warn("通道 capabilities 不是 JSON 对象,已忽略: {}", abbrev(value));
return null;
}
return objectMapper.convertValue(node, new TypeReference<Map<String, Object>>() {
});
} catch (Exception e) {
log.warn("通道 capabilities 解析失败,已忽略: {}", abbrev(value));
return null;
}
}
private static String abbrev(String value) {
return value.length() <= 120 ? value : value.substring(0, 120);
}
@SuppressWarnings("unchecked")
private static List<Map<String, Object>> children(Map<String, Object> node) {
return (List<Map<String, Object>>) node.get("children");
}
private static void collectAreaKeys(AggBasedataArea area,
Map<String, AggBasedataArea> areaIndex,
Set<String> keys) {
AggBasedataArea current = area;
while (current != null) {
String key = areaKey(current.getStationCode(), current.getAreaId());
if (!keys.add(key)) break;
current = current.getParentId() == null
? null
: areaIndex.get(areaKey(current.getStationCode(), current.getParentId()));
}
}
private static boolean matchesName(AggBasedataEqpbook eqpbook, String value) {
if (!StringUtils.hasText(value)) return true;
return value.equals(eqpbook.getPatrolDeviceCode())
|| (eqpbook.getPatrolDeviceName() != null
&& eqpbook.getPatrolDeviceName().contains(value));
}
private static boolean matchesDeviceSource(AggBasedataEqpbookChannel channel, String deviceSource) {
if (!StringUtils.hasText(deviceSource)) {
return true;
}
String expected = deviceSource.trim();
String value = channel.getDeviceSource();
String actual = value == null ? "" : value.trim();
if ("其他".equals(expected)) {
return actual.isEmpty() || "其他".equals(actual);
}
return expected.equals(actual);
}
private static boolean matchesUseMode(AggBasedataEqpbookChannel channel, String useMode) {
if (!StringUtils.hasText(useMode)) {
return true;
}
String actual = channel.getUseMode() == null ? "" : channel.getUseMode().trim();
for (String expected : useMode.split(",")) {
String value = expected.trim();
if (!value.isEmpty() && value.equals(actual)) {
return true;
}
}
return false;
}
private static boolean matchesType(AggBasedataEqpbook eqpbook, String flag) {
if (!StringUtils.hasText(flag)) return true;
String type = eqpbook.getDeviceType();
if ("nvr".equals(flag)) return "11".equals(type);
if ("ipc".equals(flag)) return type == null || (!"11".equals(type) && !"20".equals(type));
if ("robotAndUav".equals(flag)) return "1".equals(type) || "2".equals(type)
|| "3".equals(type) || "13".equals(type);
if ("uav".equals(flag)) return "13".equals(type);
return true;
}
private static String areaKey(String stationCode, Long areaId) {
return stationCode + ":area:" + areaId;
}
private static String eqpbookKey(String stationCode, Long eqpbookId) {
return stationCode + ":eqpbook:" + eqpbookId;
}
}

+ 104
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/InspectionReportSyncService.java View File

@ -0,0 +1,104 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggInspectionReport;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggInspectionReportMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 巡视报告镜像同步。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class InspectionReportSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggInspectionReportMapper reportMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncAllStations() {
for (AggStation station : stationRegistry.listEnabled()) {
syncStation(station);
}
}
public void syncStation(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllInspectionReports(
station, props.getSync().getBase().getPageSize(), 100);
List<Long> lineIds = new ArrayList<>();
for (JsonNode row : rows) {
AggInspectionReport report = map(row, station);
if (report == null) {
continue;
}
reportMapper.upsert(report);
lineIds.add(report.getLineId());
upserted++;
}
deletedMarked = reportMapper.markDeletedExcept(station.getStationCode(), lineIds);
message = "rows=" + rows.size();
log.info("station={} inspection_report sync ok: upserted={}, deletedMarked={}",
station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} inspection_report sync failed: {}",
station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "report", "FULL", status, upserted,
deletedMarked, message, started, LocalDateTime.now());
}
private AggInspectionReport map(JsonNode row, AggStation station) {
Long lineId = Rows.longVal(row, "lineId");
if (lineId == null) {
return null;
}
AggInspectionReport report = new AggInspectionReport();
report.setStationCode(station.getStationCode());
report.setLineId(lineId);
report.setStationName(Rows.text(row, "stationName"));
report.setVoltLevel(Rows.text(row, "voltLevel"));
report.setStationType(Rows.text(row, "stationType"));
report.setInspectionDate(Rows.date(row, "inspectionDate"));
report.setInspectionTaskName(Rows.text(row, "inspectionTaskName"));
report.setEnvInfo(Rows.text(row, "envInfo"));
report.setInspectionStartTime(Rows.date(row, "inspectionStartTime"));
report.setInspectionEndTime(Rows.date(row, "inspectionEndTime"));
report.setPatrolStatistics(Rows.text(row, "patrolStatistics"));
report.setCheckPerson(Rows.text(row, "checkPerson"));
report.setCheckTime(Rows.date(row, "checkTime"));
report.setDescription(Rows.text(row, "description"));
report.setFilePath(Rows.text(row, "filePath"));
report.setTaskId(Rows.text(row, "taskId"));
report.setTaskResultId(Rows.text(row, "taskResultId"));
report.setTaskPatrolledId(Rows.text(row, "taskPatrolledId"));
report.setLocation(Rows.text(row, "location"));
report.setFilter(Rows.text(row, "filter"));
report.setSyncedAt(LocalDateTime.now());
report.setIsDeleted(0);
return report;
}
}

+ 120
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/PatrolTaskSyncService.java View File

@ -0,0 +1,120 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.calendar.AggTaskCalendarService;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggPatrolTask;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggPatrolTaskMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 任务方案同步服务(源 patrol_task,接口 /patrol/task/list,量小,周期全量对账)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PatrolTaskSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggPatrolTaskMapper aggPatrolTaskMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
private final AggTaskCalendarService calendarService;
public void syncFullAllStations() {
stationRegistry.listEnabled().parallelStream().forEach(this::syncFull);
}
public void syncFull(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllRows(station, "patrol/task/list",
props.getSync().getBase().getPageSize(), 100, "taskId");
List<Long> ids = new ArrayList<>();
for (JsonNode row : rows) {
AggPatrolTask p = map(row, station);
if (p == null) {
continue;
}
aggPatrolTaskMapper.upsert(p);
if (p.getTaskId() != null) {
ids.add(p.getTaskId());
}
upserted++;
}
deletedMarked = aggPatrolTaskMapper.markDeletedExcept(station.getStationCode(), ids);
calendarService.evictStation(station.getStationCode());
message = "rows=" + rows.size();
log.info("station={} patrol_task FULL ok: upserted={}, deletedMarked={}", station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} patrol_task FULL failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "taskplan", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
private AggPatrolTask map(JsonNode r, AggStation station) {
Long taskId = Rows.longVal(r, "taskId");
if (taskId == null) {
return null;
}
AggPatrolTask p = new AggPatrolTask();
p.setStationCode(station.getStationCode());
p.setTaskId(taskId);
p.setTaskCode(Rows.text(r, "taskCode"));
p.setTaskName(Rows.text(r, "taskName"));
p.setType(Rows.text(r, "type"));
p.setPriority(Rows.text(r, "priority"));
p.setDeviceLevel(Rows.text(r, "deviceLevel"));
p.setDeviceList(Rows.text(r, "deviceList"));
p.setFixedStartTime(Rows.date(r, "fixedStartTime"));
p.setIsEnable(Rows.text(r, "isEnable", "isenable"));
p.setFileStatus(Rows.text(r, "fileStatus"));
p.setExecutionStatus(Rows.text(r, "executionStatus"));
p.setTaskCycle(Rows.text(r, "taskCycle"));
p.setCycleMonth(Rows.text(r, "cycleMonth"));
p.setCycleWeek(Rows.text(r, "cycleWeek"));
p.setCycleExecuteTime(Rows.text(r, "cycleExecuteTime"));
p.setCycleStartTime(Rows.date(r, "cycleStartTime"));
p.setCycleEndTime(Rows.date(r, "cycleEndTime"));
p.setIntervalNumber(Rows.intVal(r, "intervalNumber"));
p.setIntervalType(Rows.text(r, "intervalType"));
p.setIntervalExecuteTime(Rows.text(r, "intervalExecuteTime"));
p.setIntervalStartTime(Rows.date(r, "intervalStartTime"));
p.setIntervalEndTime(Rows.date(r, "intervalEndTime"));
p.setInvalidStartTime(Rows.date(r, "invalidStartTime"));
p.setInvalidEndTime(Rows.date(r, "invalidEndTime"));
p.setDevType(Rows.text(r, "devType"));
p.setAreaId(Rows.text(r, "areaId"));
p.setAreaName(Rows.text(r, "areaName"));
p.setPointType(Rows.text(r, "pointType"));
Integer totalPoints = Rows.intVal(r, "totalPoints");
p.setTotalPoints(totalPoints == null ? 0 : totalPoints);
p.setExecutionMode(Rows.text(r, "executionMode"));
p.setDrafter(Rows.text(r, "drafter"));
p.setSpecifiedAlg(Rows.text(r, "specifiedAlg"));
p.setCreateTime(Rows.date(r, "createTime"));
p.setSyncedAt(LocalDateTime.now());
p.setIsDeleted(0);
return p;
}
}

+ 99
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/PointSyncService.java View File

@ -0,0 +1,99 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggBasedataPatrolpoint;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggBasedataPatrolpointMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 巡检点位同步服务(基础数据全量:源 basedata_patrolpoint)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PointSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggBasedataPatrolpointMapper aggPointMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncAllStations() {
for (AggStation station : stationRegistry.listEnabled()) {
syncStation(station);
}
}
public void syncStation(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllPatrolPoints(station,
props.getSync().getBase().getPageSize(), 500);
List<Long> ids = new ArrayList<>();
for (JsonNode row : rows) {
AggBasedataPatrolpoint p = map(row, station);
if (p == null) {
continue;
}
aggPointMapper.upsert(p);
if (p.getPatrolPointId() != null) {
ids.add(p.getPatrolPointId());
}
upserted++;
}
deletedMarked = aggPointMapper.markDeletedExcept(station.getStationCode(), ids);
message = "rows=" + rows.size();
log.info("station={} point sync ok: upserted={}, deletedMarked={}", station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} point sync failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "point", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
private AggBasedataPatrolpoint map(JsonNode r, AggStation station) {
Long pointId = Rows.longVal(r, "patrolpointId");
if (pointId == null) {
return null;
}
AggBasedataPatrolpoint p = new AggBasedataPatrolpoint();
p.setStationCode(station.getStationCode());
p.setPatrolPointId(pointId);
p.setPatrolPointCode(Rows.text(r, "patrolpointCode"));
p.setPatrolPointName(Rows.text(r, "patrolpointName"));
p.setMainDeviceId(Rows.longVal(r, "mainDeviceId"));
p.setDeviceId(Rows.longVal(r, "deviceId"));
p.setAreaId(Rows.longVal(r, "areaId"));
p.setAreaName(Rows.text(r, "areaName"));
p.setPatrolContent(Rows.text(r, "patrolContent"));
p.setPurpose(Rows.text(r, "purpose"));
p.setPatrolTypeId(Rows.text(r, "patrolTypeId"));
p.setPointImptLevel(Rows.text(r, "pointImptLevel"));
p.setAlarmThreshold(Rows.text(r, "alarmThreshold"));
p.setAlgSubtypeIds(Rows.text(r, "algSubtypeIds"));
p.setUpdateTime(Rows.date(r, "updateTime"));
p.setSyncedAt(LocalDateTime.now());
p.setIsDeleted(0);
return p;
}
}

+ 130
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/ResultAnalysisSyncService.java View File

@ -0,0 +1,130 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggResultAnalysis;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggResultAnalysisMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import com.inspect.base.core.enums.DefectLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* result_analysis 汇聚同步服务。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ResultAnalysisSyncService {
private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggResultAnalysisMapper resultAnalysisMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncLast12MonthsAllStations() {
stationRegistry.listEnabled().forEach(this::syncLast12Months);
}
public void syncRecentAllStations() {
LocalDateTime end = LocalDateTime.now();
LocalDateTime begin = end.minusDays(7);
stationRegistry.listEnabled().forEach(station -> syncRange(station, begin, end, "RECENT"));
}
public void syncLast12Months(AggStation station) {
LocalDateTime begin = YearMonth.now().minusMonths(11).atDay(1).atStartOfDay();
LocalDateTime end = YearMonth.now().plusMonths(1).atDay(1).atStartOfDay();
syncRange(station, begin, end, "FULL_12M");
}
private void syncRange(AggStation station, LocalDateTime begin, LocalDateTime end, String syncType) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
String message = null;
try {
Map<String, String> query = new HashMap<>();
query.put("beginTime", begin.format(DATE_TIME));
query.put("endTime", end.minusSeconds(1).format(DATE_TIME));
List<JsonNode> rows = stationApiClient.fetchAllRows(station, "patrol/analysis/agg/list",
props.getSync().getBase().getPageSize(), 1000, query, "lineId");
for (JsonNode row : rows) {
AggResultAnalysis item = map(row, station);
if (item == null) {
continue;
}
resultAnalysisMapper.upsert(item);
upserted++;
}
message = "rows=" + rows.size();
log.info("station={} result_analysis {} ok: upserted={}", station.getStationCode(), syncType, upserted);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} result_analysis {} failed: {}", station.getStationCode(), syncType, e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "analysis", syncType, status, upserted, 0,
message, started, LocalDateTime.now());
}
private AggResultAnalysis map(JsonNode row, AggStation station) {
Long lineId = Rows.longVal(row, "lineId");
if (lineId == null) {
return null;
}
String resultType = Rows.text(row, "resultType");
AggResultAnalysis item = new AggResultAnalysis();
item.setStationCode(station.getStationCode());
item.setLineId(lineId);
item.setResultType(resultType);
item.setBusinessId(Rows.text(row, "businessId"));
item.setResultContent(Rows.text(row, "resultContent"));
item.setResStatus(Rows.text(row, "resStatus"));
item.setPointStatus(Rows.text(row, "pointStatus"));
item.setSuggestion(Rows.text(row, "suggestion"));
item.setObjectId(Rows.text(row, "objectId"));
item.setDescription(Rows.text(row, "description"));
item.setRequestId(Rows.text(row, "requestId"));
item.setTaskPatrolId(Rows.text(row, "taskPatrolId"));
item.setAlgType(Rows.text(row, "algType"));
item.setResValue(Rows.text(row, "resValue"));
item.setResCode(Rows.text(row, "resCode"));
item.setResConf(Rows.text(row, "resConf"));
item.setResDesc(Rows.text(row, "resDesc"));
item.setResult(Rows.text(row, "result"));
item.setResImgUrl(Rows.text(row, "resImgUrl"));
item.setImageNormalUrlPath(Rows.text(row, "imageNormalUrlPath"));
item.setResPos(Rows.jsonText(row, "resPos"));
item.setFilter(Rows.text(row, "filter"));
item.setOriginalValue(Rows.text(row, "originalValue"));
Integer defectLevel = Rows.intVal(row, "defectLevel");
item.setDefectLevel(defectLevel == null ? deriveDefectLevel(resultType) : defectLevel);
item.setCreateTime(Rows.date(row, "createTime"));
item.setSyncedAt(LocalDateTime.now());
item.setIsDeleted(0);
return item;
}
private static int deriveDefectLevel(String resultType) {
if ("0".equals(resultType)) return DefectLevel.NORMAL.getCode();
if ("2".equals(resultType)) return DefectLevel.MINOR.getCode();
return DefectLevel.NONE.getCode();
}
}

+ 47
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/StationRegistry.java View File

@ -0,0 +1,47 @@
package com.inspect.agg.service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggStationMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 站点注册表(DB 维护):查询走 Mapper XML
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class StationRegistry {
private final AggStationMapper aggStationMapper;
/** 所有启用站点 */
public List<AggStation> listEnabled() {
List<AggStation> list = aggStationMapper.selectEnabled();
log.info("listEnabled: {}", list);
return list;
}
/** 全部站点(含停用) */
public List<AggStation> listAll() {
return aggStationMapper.selectAllOrdered();
}
/** 按编码查站点(不存在抛异常) */
public AggStation requireByCode(String stationCode) {
if (!StringUtils.hasText(stationCode)) {
throw new IllegalArgumentException("stationCode 不能为空");
}
AggStation station = aggStationMapper.selectByCode(stationCode);
if (station == null) {
throw new IllegalArgumentException("站点不存在: " + stationCode);
}
return station;
}
}

+ 147
- 0
inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/service/TaskExecSyncService.java View File

@ -0,0 +1,147 @@
package com.inspect.agg.service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.inspect.agg.config.AggProperties;
import com.inspect.agg.entity.AggPatrolTaskStatus;
import com.inspect.agg.entity.AggStation;
import com.inspect.agg.mapper.AggPatrolTaskStatusMapper;
import com.inspect.agg.station.StationApiClient;
import com.inspect.agg.support.Rows;
import com.inspect.agg.support.SyncLogWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 任务执行同步服务(动态数据)。
* 站点侧 patrol_task_status 无 update_time,无法时间水位:
* - ACTIVE:高频拉 taskState=2(执行中)子集,满足 1 分钟新鲜度
* - FULL:周期全量 + 对账删除,覆盖终态/状态变化
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TaskExecSyncService {
private final StationRegistry stationRegistry;
private final StationApiClient stationApiClient;
private final AggPatrolTaskStatusMapper aggTaskExecMapper;
private final SyncLogWriter syncLogWriter;
private final AggProperties props;
public void syncActiveAllStations() {
stationRegistry.listEnabled().parallelStream().forEach(this::syncActive);
}
public void syncFullAllStations() {
stationRegistry.listEnabled().parallelStream().forEach(this::syncFull);
}
/** 高频:只拉执行中任务 */
public void syncActive(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllRows(station, "patrol/taskstatus/list",
props.getSync().getBase().getPageSize(), 50,
Collections.singletonMap("taskState", "2"), "lineId");
for (JsonNode row : rows) {
if (upsert(row, station)) {
upserted++;
}
}
message = "rows=" + rows.size();
log.info("station={} task ACTIVE ok: upserted={}", station.getStationCode(), upserted);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} task ACTIVE failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "task", "ACTIVE", status, upserted, 0,
message, started, LocalDateTime.now());
}
/** 周期:全量拉取 + 对账删除 */
public void syncFull(AggStation station) {
LocalDateTime started = LocalDateTime.now();
String status = "SUCCESS";
int upserted = 0;
int deletedMarked = 0;
String message = null;
try {
List<JsonNode> rows = stationApiClient.fetchAllRows(station, "patrol/taskstatus/list",
500, 100, "lineId");
List<Long> ids = new ArrayList<>();
for (JsonNode row : rows) {
if (upsert(row, station) && row.hasNonNull("lineId")) {
ids.add(row.get("lineId").asLong());
}
}
deletedMarked = aggTaskExecMapper.markDeletedExcept(station.getStationCode(), ids);
upserted = ids.size();
message = "rows=" + rows.size();
log.info("station={} task FULL ok: upserted={}, deletedMarked={}", station.getStationCode(), upserted, deletedMarked);
} catch (Exception e) {
status = "FAILED";
message = Rows.abbrev(e.getMessage(), 480);
log.error("station={} task FULL failed: {}", station.getStationCode(), e.getMessage());
}
syncLogWriter.write(station.getStationCode(), "task", "FULL", status, upserted, deletedMarked,
message, started, LocalDateTime.now());
}
private boolean upsert(JsonNode r, AggStation station) {
AggPatrolTaskStatus e = map(r, station);
if (e == null) {
return false;
}
aggTaskExecMapper.upsert(e);
return true;
}
private AggPatrolTaskStatus map(JsonNode r, AggStation station) {
Long lineId = Rows.longVal(r, "lineId");
if (lineId == null) {
return null;
}
AggPatrolTaskStatus e = new AggPatrolTaskStatus();
e.setStationCode(station.getStationCode());
e.setLineId(lineId);
e.setTaskPatrolledId(Rows.text(r, "taskPatrolledId"));
e.setTaskName(Rows.text(r, "taskName"));
e.setTaskCode(Rows.text(r, "taskCode"));
e.setTaskState(Rows.text(r, "taskState"));
e.setPlanStartTime(Rows.date(r, "planStartTime"));
e.setStartTime(Rows.date(r, "startTime"));
e.setEndTime(Rows.date(r, "endTime"));
e.setTaskProgress(Rows.text(r, "taskProgress"));
e.setTaskEstimatedTime(Rows.text(r, "taskEstimatedTime"));
e.setPatrolType(Rows.text(r, "patrolType"));
e.setPosType(Rows.text(r, "posType"));
e.setCode(Rows.text(r, "code"));
e.setPos(Rows.text(r, "pos"));
e.setExecType(Rows.text(r, "execType"));
e.setTaskType(Rows.text(r, "taskType"));
e.setMainId(Rows.text(r, "mainId"));
Integer qxNum = Rows.intVal(r, "qxNum");
e.setQxNum(qxNum == null ? 0 : qxNum);
String fileStatus = Rows.text(r, "fileStatus");
e.setFileStatus(fileStatus == null ? "0" : fileStatus);
e.setSummary(Rows.jsonText(r, "summary"));
e.setDescription(Rows.text(r, "description"));
e.setCreateTime(Rows.date(r, "createTime"));
e.setSyncedAt(LocalDateTime.now());
e.setIsDeleted(0);
return e;
}
}

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

Loading…
Cancel
Save