Compare commits

..

38 Commits

Author SHA1 Message Date
7d3130c4fe feat: 修改隐患排查计划列表待审核人数统计逻辑,改为只统计审核中(status=2,5)的数量 2026-07-28 16:47:10 +08:00
96d7f8d803 feat(隐患排查计划): 新增待审核人数统计字段及计算逻辑 2026-07-28 15:56:27 +08:00
0adba6f5cb feat(photo-evidence): 实现图片水印异常修复全流程功能
抽离并重构原有图片水印生成逻辑,统一输出jpeg格式,避免因异常文件后缀产生坏数据
新增异常水印图片的分页查询、预览确认、单条修复、批量异步修复及任务状态查询接口
新增对应的控制器、业务服务类与数据传输对象
补充图片水印异常修复的完整方案文档
2026-07-01 23:02:00 +08:00
668ae04530 feat: 百度api key更新 2026-06-25 20:23:43 +08:00
116dca0543 修复(测试配置): 更新测试环境的服务IP地址
将application-test.yml中MySQL、Redis及Gotenberg的服务地址从192.168.129.64替换为172.21.143.43
2026-06-25 16:49:21 +08:00
c54de034d2 chore(开发配置): 更新开发环境数据库连接配置
调整数据库连接地址为本地192.168.24.254:50001,数据库名称改为color_20260608,同时更新对应的访问密码
2026-06-25 14:07:27 +08:00
efab0eeb18 refactor(vehicleThreeInspect): 优化批量审核与隐患治理流程,完善日志与异常处理
- 为车辆三检批量审核流程添加多阶段详细日志便于排查问题
- 重构批量审核筛选逻辑,新增各跳过原因统计及无候选数据提前抛出异常
- 修复隐患治理流程重复创建问题,新增重复记录检查逻辑
- 优化隐患治理流程的参数处理与日志输出,新增审核结果翻译工具方法
- 更新流程回调日志,补充实例ID、流程编码等上下文信息
2026-06-23 00:16:13 +08:00
b1913abab5 车辆筛选条件修改 2026-06-17 17:30:04 +08:00
f21c57943c 车辆三检批量审核 2026-06-17 10:36:39 +08:00
c737dd5571 排序 2026-06-08 17:28:09 +08:00
7a40f887a2 fix(security/HotVehicleThreeInspect): 修正热点车辆三检审核状态计算逻辑
移除原硬编码的3个审核阶段限制,改为通过非空审核阶段数与已通过数相等判断审核完成状态
2026-06-08 15:01:07 +08:00
5133b20cca docs: 文档提交 2026-06-06 16:45:12 +08:00
15f20d9e28 docs: add 驾驶员默认角色缺失排查与修复文档 2026-06-06 15:45:43 +08:00
bcc39277b0 chore: 去掉无效编码 2026-06-05 12:57:28 +08:00
98a83bc79c feat(system-maintenance): 新增媒体资源元数据修复功能
实现完整的媒体资源元数据修复业务模块,提供提交修复任务与查询任务状态的REST接口,支持修复媒体资源的大小和时长信息,适配普通文件和HLS流媒体,内置域名白名单、下载限制及任务缓存清理能力。
2026-06-05 12:35:19 +08:00
5b87e2f7ca feat: 报表模板同步功能 2026-06-04 09:54:10 +08:00
e76b3a35aa fix(securityManagement): 修复移动端巡检列表分页查询SQL逻辑
重构该Mapper的分页查询SQL,调整表关联与过滤条件,优化状态计算、搜索匹配及排序规则,修复原查询的数据统计与展示问题。
2026-06-03 21:07:53 +08:00
977bd0a2bb feat: 隐患排查补查功能 2026-06-03 17:45:53 +08:00
38b393cafa feat(hiddenDangerPlan): 新增隐患排查计划的完成未完成数量统计
为隐患排查计划VO新增已完成数、未完成数统计字段,编写自定义XML SQL统计各计划的完成情况,更新Mapper和Service使用新的查询逻辑
2026-06-03 10:41:40 +08:00
041bdf1a32 feat(企业安全管理): 新增唯一性校验,修复多记录查询问题并优化业务逻辑
- 修复角色查询方法,处理多条有效安全管理员记录场景,改为获取最新记录并添加警告日志
- 在安全管理人员新增/更新时新增唯一性校验,阻止同一企业下重复手机号和用户的有效记录
- 重构企业负责人业务逻辑,拆分通用方法,优化现有记录查找逻辑,新增旧负责人数据清理功
2026-06-02 17:33:31 +08:00
d9ce694835 三检历史经纬度定位 2026-06-02 15:27:47 +08:00
c29596337f 新增车辆三检的三检历史记录列表 2026-06-02 10:02:09 +08:00
71558b072d 隐患排查加计划id筛选条件 2026-06-01 16:39:02 +08:00
afaa25489d Merge remote-tracking branch 'origin/main' 2026-06-01 14:38:37 +08:00
9ada784a17 驾驶证,行驶证,从业资格证
改为到期前30天提醒并发消息到管理员账号,单个驾驶员账号也要收到自己证件到期的消息
2026-06-01 14:38:23 +08:00
eab8a4693d feat(vehicleThreeInspect): 新增车辆三检聚合列表分页查询功能
新增聚合视图对象与全链路查询逻辑,实现车辆三检出车、行车、收车全流程数据的聚合展示,同时删除废弃的旧运维备注配置文件。
2026-06-01 10:19:03 +08:00
7f79698681 feat: 删除无用内容 2026-06-01 10:09:01 +08:00
0ebdcdb6bf 隐患排查bug 2026-05-31 20:30:05 +08:00
5b3c300c75 安全学习照片回显 2026-05-30 16:31:40 +08:00
7254f7c35f 隐患排查初版 2026-05-30 16:00:20 +08:00
d48ecdd89c 新增统一禁学判断组件 2026-05-29 15:15:37 +08:00
6d288e7d3e 车辆切换 2026-05-29 12:55:28 +08:00
e1ce6413ba Merge remote-tracking branch 'origin/main' 2026-05-28 16:39:54 +08:00
52dbbe5cc3 车辆切换 2026-05-28 16:39:45 +08:00
d5f5ef3f37 feat: 已过期的学习无法查看学习明细,学习记录无论过期不过期,关闭不关闭,明细都能查看 2026-05-28 15:07:31 +08:00
627f155564 新增接口,前端调用后,提示车辆是否已被出车 2026-05-28 14:57:45 +08:00
4a77f3e431 一辆车同时被不同人切换 2026-05-28 14:41:08 +08:00
0cc73dc267 车辆切换 2026-05-28 14:09:01 +08:00
143 changed files with 6976 additions and 1852 deletions

View File

@@ -0,0 +1,103 @@
# 隐患排查审核不通过重提方案
## 目标
- 支持企业审核不通过后,驾驶员再次编辑并重新提交。
- 每次审核不通过时,保存当次提交快照、驳回原因、照片、签名等信息。
- 详情接口可返回多次审核不通过历史,便于企业端查看。
## 后端改动
### 主表 `hot_hidden_danger_inspection`
新增字段:
- `submit_version`:提交版本号
- `audit_result`:审核结果,`PASS` / `REJECT`
- `reject_count`:驳回次数
- `last_reject_reason`:最近一次驳回原因
- `last_reject_time`:最近一次驳回时间
- `last_reject_by_id`最近一次驳回人ID
- `last_reject_by_name`:最近一次驳回人姓名
### 新表 `hot_hidden_danger_inspection_audit_history`
用于保存每次审核不通过时的完整快照:
- 主单ID、计划ID、项目信息
- 提交版本号、审核轮次
- 驳回原因、审核人、审核时间、审核签名
- 当次排查内容、隐患描述、附件、排查人签名、排查人、排查时间
- `submit_snapshot_json` 作为完整快照冗余
## 业务逻辑
### 驾驶员提交
- 首次提交时,沿用原有 `updateByBo` 提交流程。
- 驳回后再次编辑提交时,只要主单已经被回调置为:
- `status = 1`
- `flow_status = REJECTED`
- `instance_id = null`
- 再次保存后会重新启动 `HIDDEN_DANGER_CHECK` 流程。
- 每次重提时:
- `submit_version + 1`
- 清空上一轮审核结论、审核签名、评估人等中间字段
- 状态回到 `status = 2`
### 企业审核
审核新增两种结果:
- `PASS`:审核通过
- `REJECT`:审核不通过
处理规则:
- `PASS + auditHasDanger = 0`
- 流程通过
- 主单最终状态在回调中置为 `3`
- `PASS + auditHasDanger = 1`
- 流程通过
- 主单在回调中置为 `6`
- 自动创建隐患治理流程
- `REJECT`
- 先写入驳回历史表
- 主单记录最近一次驳回信息
- 流程按驳回处理
- 回调把主单恢复为可再次编辑状态
### 流程回调
`HiddenDangerCheckCallback` 调整为:
- 成功:
- `flow_status = APPROVED`
- 无隐患:`status = 3`
- 有隐患:`status = 6`
- 仅在成功且存在隐患时创建治理流程
- 驳回:
- `flow_status = REJECTED`
- `status = 1`
- `instance_id = null`
- 允许驾驶员再次提交
## 详情接口
`queryById` 返回主单详情时,额外附带:
- `rejectHistoryList`
该字段按驳回轮次倒序返回,可直接给前端做历史展开。
## SQL 文件
SQL 已写入:
- `sql/update_hidden_danger_inspection_reject_history.sql`
## 注意事项
- 当前版本只改后端,未改前端和移动端展示。
- 前端后续只要在详情页读取 `rejectHistoryList` 即可展示多次驳回历史。
- 审核接口调用时建议明确传 `auditResult`,不要再仅依赖 `auditHasDanger` 推断审核结果。

View File

@@ -111,3 +111,200 @@ ADD COLUMN reject_reason varchar(500) NULL COMMENT '审核不通过原因';
- 详情接口、列表接口、导出接口会返回 `rejectReason` 字段。
- 审核驳回通知优先展示 `rejectReason`,没有时才回退展示 `auditResult`
## 6. 三检历史记录接口
### 6.1 新增字段
- `inspectNo`:三检记录编号,规则为 `SJ + 三检创建日期(yyyyMMdd) + 车牌号后3位 + 4位随机数`
- `inspectLocation`:检查地点,出车前、行车中、收车后三个阶段都支持保存
### 6.2 历史列表接口
- 路径:`GET /securityManagement/vehicleThreeInspect/history/list`
- 说明:按 `inspectId` 聚合查询一轮三检历史记录,支持分页
返回核心字段:
- `inspectId`三检流程ID
- `inspectNo`:三检记录编号
- `plateNumber`:车牌号
- `inspectorName`:检查人
- `startTime`:检查开始时间(出车提交时间)
- `endTime`:检查结束时间(收车提交时间)
- `auditorName`:审核人
- `outInspectLocation`:出车前检查地点
- `drivingInspectLocation`:行车中检查地点
- `backInspectLocation`:收车后检查地点
- `outCheck` / `drivingCheck` / `backCheck`:三个阶段详情
### 6.3 历史详情接口
- 路径:`GET /securityManagement/vehicleThreeInspect/history/{inspectId}`
- 说明:返回指定 `inspectId` 下三个阶段的车辆三检详情
### 6.4 数据库脚本
新增脚本:
- `sql/alter_hot_vehicle_three_inspect_history_fields_20260601.sql`
脚本内容包含:
-`hot_vehicle_three_inspect` 增加 `inspect_no``inspect_location` 字段
- 对历史数据回填 `inspect_no`
## 7. 多车辆批量审核接口
### 7.1 可批量审核列表接口
- 路径:`GET /securityManagement/vehicleThreeInspect/multiVehicleBatchAudit/list`
- 说明:分页查询当前登录人员可进行多车辆批量审核的车辆三检列表
### 7.2 查询参数
- `pageNum`:页码
- `pageSize`:每页条数,前端默认 `20`
- `plateNumber`:车牌号,支持筛选
- `companyId`企业ID可选不传时后端默认取当前登录企业
请求示例:
```http
GET /securityManagement/vehicleThreeInspect/multiVehicleBatchAudit/list?pageNum=1&pageSize=20&plateNumber=川A
```
### 7.3 列表筛选规则
后端只会返回同时满足以下条件的三检流程:
1. 当前三检流程已经存在`收车后检查`记录。
2. 当前三检流程下存在`审核人是当前登录账号`的未审核记录。
3. 数据属于当前登录企业。
不满足上述条件的数据不会出现在列表中。
### 7.4 返回字段
后端返回类型为 `HotVehicleThreeInspectSummaryVo`,前端当前主要使用以下字段:
- `inspectId`三检流程ID
- `vehicleId`车辆ID
- `plateNumber`:车牌号
- `inspectorName`:检查人姓名
- `auditorName`:审核人姓名
- `startTime`:开始时间
- `endTime`:结束时间
- `outCheck` / `drivingCheck` / `backCheck`:三个阶段详情,查看详情时可直接复用
返回示例:
```json
{
"code": 200,
"msg": "操作成功",
"rows": [
{
"inspectId": 1001,
"vehicleId": 501,
"plateNumber": "川A12345",
"inspectorName": "张三",
"auditorName": "李四",
"startTime": "2026-06-17 08:00:00",
"endTime": "2026-06-17 18:00:00"
}
],
"total": 1
}
```
## 8. 多车辆批量审核提交接口
### 8.1 接口信息
- 路径:`POST /securityManagement/vehicleThreeInspect/multiVehicleBatchAudit`
- 说明:对多辆车的车辆三检进行批量审核
- 处理方式:新接口只负责组装批量审核数据,底层仍复用原有 `batchAudit``doAudit` 逻辑
### 8.2 请求体
```json
{
"inspectIds": [1001, 1002, 1003],
"hasHiddenDanger": 1,
"evaluatorId": 2001,
"auditTime": "2026-06-17 18:30:00",
"auditResult": "存在隐患,需整改",
"rejectReason": "轮胎磨损严重",
"auditorSignImgUrl": "https://example.com/sign.png"
}
```
### 8.3 请求字段说明
- `inspectIds`三检流程ID集合必填
- `hasHiddenDanger`:是否存在隐患,必填;`0=否``1=是`
- `evaluatorId`评估人ID`hasHiddenDanger = 1` 时必填
- `auditTime`:审核时间;不传时后端默认取当前时间
- `auditResult`:审核结论;不传时后端自动生成
- `rejectReason`:审核不通过原因;有隐患时建议传入
- `auditorSignImgUrl`:审核人员签名图片地址,必填
### 8.4 自动生成规则
-`auditTime` 为空时,后端自动使用当前时间。
-`auditResult` 为空时:
- `hasHiddenDanger = 0`:自动生成 `通过`
- `hasHiddenDanger = 1` 且传了 `rejectReason`:自动生成 `不通过:{rejectReason}`
- `hasHiddenDanger = 1` 且未传 `rejectReason`:自动生成 `不通过`
### 8.5 批量审核校验规则
后端会对每个 `inspectId` 依次校验:
1. 三检流程必须存在。
2. 当前流程必须已完成收车检查,否则报错:`{车牌号}尚未完成收车检查,不可批量审核`
3. 当前流程中必须存在`审核人是当前登录人`的未审核数据,否则报错:`{车牌号}没有当前账号可审核的未审核数据`
4. 只有当前登录企业下的数据才会参与组装。
5. 组装后的明细仍会走原有单条审核校验,包括:
- 流程任务ID不能为空
- 三检记录ID不能为空
- 审核时间不能早于检查时间
- 审核签名校验
- 流程审批人校验
### 8.6 实际审核范围
每个 `inspectId` 并不是只审核一条数据,而是会自动筛出该三检流程下所有满足以下条件的阶段记录一起审核:
- 审核人等于当前登录账号
- 当前记录尚未审核完成
- 当前记录存在流程任务 `taskId`
因此一次多车辆批量审核,最终可能会展开成多条实际审核明细。
### 8.7 有隐患时的处理
-`hasHiddenDanger = 1` 时,审核流程按不通过处理。
- 流程结束后,会按每一条三检明细分别创建隐患治理数据。
- 如果本次批量审核展开后共有 N 条三检明细,则会生成 N 条隐患治理数据,不会合并成 1 条。
### 8.8 返回结果
成功返回:
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
失败时返回业务异常,例如:
- `三检流程ID不能为空`
- `存在隐患时评估人不能为空`
- `未查询到可批量审核的车辆三检数据`
- `{车牌号}尚未完成收车检查,不可批量审核`
- `{车牌号}没有当前账号可审核的未审核数据`

View File

@@ -0,0 +1,421 @@
# 图片水印后缀异常兜底与补救方案
## 1. 背景
现有图片取证水印流程位于 [PhotoEvidenceServiceImpl.java:L51-L149](file:///g:/code/shw/hot/wucaixing-full/wucaixing-backend/src/main/java/com/hotwj/platform/common/photoEvidence/service/impl/PhotoEvidenceServiceImpl.java#L51-L149)。
当前已确认的问题链路如下:
- 前端上传 OSS 时,部分原始图片没有正确的文件后缀名。
- 原始 `sys_oss.file_suffix` 被写成了 `2``3``.2``.3` 这一类异常值。
- 后端在生成水印图时,直接使用原始 `file_suffix` 作为 `ImageIO.write` 的输出格式。
- 生成的临时文件可能为空文件,最终上传到 OSS 后变成 `0B`
- 新生成的 `sys_oss` 记录中,`ext1.fileSize=0``contentType=null`,页面预览和下载均异常。
示例数据可见 [data.json:L1-L35](file:///g:/code/shw/hot/wucaixing-full/data.json#L1-L35)
- 原图记录 `file_suffix=3`,但 `ext1.contentType=image/jpeg`
- 水印图记录 `file_suffix=.3``ext1.fileSize=0``contentType=null`
当前风险点位于 [PhotoEvidenceServiceImpl.java:L137-L149](file:///g:/code/shw/hot/wucaixing-full/wucaixing-backend/src/main/java/com/hotwj/platform/common/photoEvidence/service/impl/PhotoEvidenceServiceImpl.java#L137-L149)
,源码中也已经有 `TODO` 提示需要判断 `mime`
## 2. 根因分析
本问题的根因不是“没有后缀名”本身,而是“后端把不可信的后缀名继续当成图片编码格式使用”。
具体来说:
- 原始图片是否可读,真正应该依据的是二进制内容和 MIME不应该只依赖文件名后缀。
- 水印图不是前端上传文件,而是后端重新生成的系统产物,其输出格式应由后端统一决定。
- 只要继续信任 `sys_oss.file_suffix`,就会持续受到历史脏数据和前端上传异常的影响。
因此,兜底思路必须从“猜正确后缀”改成“后端固定输出规范”。
## 3. 线上兜底方案
### 3.1 目标
阻断新增坏数据,确保后续所有水印图都能稳定生成、上传、展示和下载。
### 3.2 方案原则
- 原图校验看内容,不看后缀。
- 水印图输出格式由后端统一指定,不继承原图后缀。
- 上传时必须带明确的 `contentType`
- 入库前必须校验结果文件非空。
### 3.3 推荐方案
建议将“图片取证水印图”统一输出为 `jpg/jpeg`
原因:
-
当前水印结果图在 [PhotoEvidenceServiceImpl.java:L261-L283](file:///g:/code/shw/hot/wucaixing-full/wucaixing-backend/src/main/java/com/hotwj/platform/common/photoEvidence/service/impl/PhotoEvidenceServiceImpl.java#L261-L283)
中已经固定为 `BufferedImage.TYPE_INT_RGB`,天然适合输出为 `JPEG`
- 业务场景是现场拍照取证,统一输出 `jpg` 与实际使用习惯一致。
- 可以彻底摆脱 `2``3``.2``.3` 这一类脏后缀的影响。
- 后续历史修复和排障标准会非常统一。
### 3.4 兜底规则
建议按以下规则处理:
1. 下载原始 OSS 文件后,先尝试按图片内容解析。
2. 只要原图字节能正常解析成图片,就允许继续加水印。
3. 水印结果图统一编码为 `jpeg`
4. 临时文件统一使用 `.jpg` 后缀。
5. 上传 OSS 时统一使用 `contentType=image/jpeg`
6. 上传前必须校验:
- `ImageIO.write` 返回成功
- 临时文件存在
- 临时文件大小大于 `0`
7. 任一校验失败,直接报错,不创建 `HotPhotoEvidence` 记录,不创建新的水印 `sys_oss` 记录。
### 3.5 兜底后的收益
- 前端即使继续上传无后缀文件,后端仍可稳定产出标准水印图。
- 生成的水印图始终具备标准后缀和标准 `contentType`
- 不再出现 `.2``.3` 这类系统生成的异常文件。
- 新增数据和历史补救数据将收敛到同一规范。
## 4. 历史补救总原则
### 4.1 必须满足的约束
本次历史补救必须满足以下硬约束:
- 必须替换原有坏掉的 `sys_oss` 记录内容。
- 必须保持原 `oss_id` 不变。
- 不能只改 `HotPhotoEvidence` 表中的 `watermarked_oss_id` 指向。
- 不能新增一条新的 `sys_oss` 记录再让业务表改引用。
原因:
- 这个 `oss_id` 不仅存在于 `HotPhotoEvidence`,还可能被其他业务表、日志记录、导出记录、接口缓存、前端页面配置等位置复用。
- 如果改成新 `oss_id`,补救范围会迅速扩散到未知引用方,风险不可控。
- 保持原 `oss_id` 不变,才能把历史补救范围收敛在“文件对象替换 + 原记录修补”这一层。
### 4.2 正确的补救思路
正确思路不是“替换原始图的 `sys_oss` 记录”,而是:
-`HotPhotoEvidence` 为主表定位坏掉的水印记录。
- 通过 `original_oss_id` 找回原始图片。
- 重新生成一张标准的水印图。
- 上传到 OSS 获得新的对象文件。
- 然后覆盖原 `watermarked_oss_id` 对应的 `sys_oss` 记录内容。
- `oss_id` 保持不变,只更新该记录指向的对象信息和元数据。
换句话说:
- 替换的是“坏水印图对应的 `sys_oss` 内容”
- 保留的是“该 `sys_oss` 的主键 `oss_id`
## 5. 历史补救识别范围
建议先做数据筛查,再执行修补。
### 5.1 强特征
以下记录可直接视为高风险坏数据:
- `sys_oss.ext1.fileSize = 0`
- `sys_oss.file_suffix` 为纯数字,如 `2``3`
- `sys_oss.file_suffix``.数字`,如 `.2``.3`
- `sys_oss.original_name``photo_evidence_xxx.2``photo_evidence_xxx.3`
- `sys_oss.ext1.contentType is null`
### 5.2 建议以 `HotPhotoEvidence` 为主表驱动
原因:
- 该表已经保留了 `originalOssId``watermarkedOssId`
- 已经保留了 `captureTimeClient``captureTimeServer`
- 已经保留了 `latitude``longitude`
- 已经保留了 `addressTextServer``addressTextClient``addressSource`
这些字段足够支撑“按历史状态重建水印图”,不需要重新依赖前端数据。
## 6. 历史补救执行方案
### 6.1 核心目标
在不改变 `watermarked_oss_id``oss_id` 的前提下,修复坏掉的水印图对象和其 `sys_oss` 元数据。
### 6.2 推荐执行步骤
1. 根据 `HotPhotoEvidence.watermarked_oss_id` 关联 `sys_oss`,筛出疑似坏水印记录。
2. 按记录读取 `original_oss_id` 对应的原始图。
3. 使用历史记录中的 `captureTimeClient` 作为水印时间。
4. 水印地址按以下优先级确定:
-`addressSource=SERVER`,取 `addressTextServer`
- 否则取 `addressTextClient`
5. 不重新调用逆地理接口,不重算地址。
6. 按统一规范重新生成 `jpg` 水印图。
7. 先上传到 OSS 新对象路径,确认对象可访问且大小正常。
8. 用新对象信息覆盖旧的 `sys_oss` 记录:
- `file_name`
- `url`
- `file_suffix`
- `original_name`
- `ext1.fileSize`
- `ext1.contentType`
- 如有必要同步更新时间和更新人
9. `oss_id` 保持不变。
10. 数据更新成功后,再删除旧的坏对象文件,或先保留待统一清理。
### 6.3 必须覆盖的字段
覆盖原 `sys_oss` 记录时,建议至少同步修正以下内容:
- `file_name`:替换为新上传对象键
- `url`:替换为新对象地址
- `file_suffix`:统一改为 `.jpg`
- `original_name`:统一规范化,例如 `photo_evidence_{ossId}.jpg`
- `ext1.fileSize`:更新为真实大小
- `ext1.contentType`:更新为 `image/jpeg`
如有以下字段被业务使用,也建议同步补齐:
- `ext1.source`
- `ext1.remark`
- `update_time`
- `update_by`
### 6.4 为什么不要重新生成新的 `sys_oss` 记录
- 会引入新的 `oss_id`
- 会导致其他业务表引用失效
- 会让修复脚本必须跨全库改引用
- 很难保证没有漏网表
- 回滚和审计也会变复杂
因此,历史补救必须采用“新对象文件 + 原记录覆盖”的模式。
## 7. 前端可视化补救方案
为了让补救方案易于操作,建议将历史补救放到后台管理界面,而不是只做一次性脚本。
### 7.1 推荐入口
建议新增后台管理页面,例如:
- 菜单名称:`水印图片修复`
- 所属模块:`系统管理``附件管理`
- 页面定位:面向运维、实施、研发,不对普通业务用户开放
前端落点建议明确放到维护模块中,参考现有维护页 [index.vue](file:///g:/code/shw/hot/wucaixing-full/wucaixing-frontend/src/views/system/maintenance/index.vue)
的维护项列表能力。
建议方案:
- 在前端维护模块 `wucaixing-frontend/src/views/system/maintenance/index.vue` 中新增一个维护项,例如 `图片水印修复`
- 点击维护项后进入专门的修复弹窗或修复页面
- 该页面承载异常筛选、预览比对、勾选记录、单条修复、批量修复、任务进度和结果查看
这样可以与现有 `媒体资源元数据修复` 的运维入口保持一致,便于统一权限、统一操作习惯、统一审计。
### 7.2 列表页建议字段
列表页建议展示以下信息,方便快速识别和人工核验:
- `evidenceId`
- `watermarkedOssId`
- `originalOssId`
- 原始图片缩略图
- 当前水印图缩略图
- `sys_oss.file_suffix`
- `ext1.fileSize`
- `contentType`
- `captureTimeClient`
- `addressSource`
- 地址文本
- 公司 / 业务类型 / 业务 ID
- 创建时间
- 修复状态
- 修复结果说明
### 7.3 列表页筛选条件
建议支持以下筛选项:
- 仅看异常记录
-`watermarkedOssId`
-`originalOssId`
- 按公司
- 按业务类型
- 按时间范围
-`fileSize=0`
-`contentType为空`
-`file_suffix` 异常
- 按修复状态
### 7.4 页面操作按钮
建议至少提供以下操作:
- `预览原图`
- `预览当前水印图`
- `生成修复预览`
- `勾选当前记录`
- `批量勾选筛选结果`
- `清空勾选`
- `执行单条修复`
- `批量修复已勾选记录`
- `导出异常清单`
- `查看修复日志`
补充要求:
- 不允许用户在未查看修复前后效果的情况下直接发起批量修复
- 批量修复应以“用户已勾选且已完成预览确认的记录”为执行范围,而不是直接对整个筛选结果无差别执行
### 7.5 单条修复建议流程
单条修复推荐流程如下:
1. 用户点开一条异常记录。
2. 页面展示:
- 原图预览
- 当前坏水印图预览
- 拟修复后的新水印预览
- 修复前后元数据对比
3. 用户点击 `确认修复`
4. 后端执行:
- 读取原图
- 按历史时间与地址重新加水印
- 上传新 OSS 对象
- 覆盖原 `sys_oss` 记录
- 记录操作日志
5. 页面回显修复结果。
这个流程适合先做试点修复和人工验证。
### 7.6 批量修复建议流程
批量修复推荐流程如下:
1. 用户先通过筛选条件锁定异常数据范围。
2. 页面展示总数、可修复数、疑似失败风险数,并支持逐条勾选待修复记录。
3. 用户先对准备修复的记录执行预览,页面必须展示:
- 原图
- 当前坏水印图
- 拟修复后的新水印图
- 修复前后元数据差异
4. 用户确认预览效果无误后,勾选本次真正要修复的记录。
5. 用户点击 `批量修复已勾选记录` 前,系统弹出二次确认,并明确显示本次修复条数。
6. 后端采用任务方式异步执行,页面可轮询查看进度。
7. 每条记录输出修复结果:
- 成功
- 原图不存在
- 原图无法解析
- 新水印生成失败
- OSS 上传失败
-`sys_oss` 覆盖失败
8. 任务完成后支持导出结果明细。
这里的关键要求是:
- 批量修复之前必须先有“预览确认”步骤
- 批量修复的对象必须是“用户主动勾选的记录”
- 不能仅凭筛选条件直接全量执行修复任务
### 7.7 为什么前端界面比一次性脚本更适合
- 更适合先小批量验证,再逐步扩大范围
- 操作人员无需直接跑脚本和改 SQL
- 每条记录可预览、可追踪、可重试
- 更方便做权限控制和操作审计
- 后续若再出现新脏数据,也可以复用同一页面处理
## 8. 后端接口建议
为支撑前端可视化操作,建议至少提供以下几类接口:
### 8.1 异常列表接口
用途:
- 按筛选条件查询疑似异常水印记录
- 返回分页列表和异常原因标签
### 8.2 预览接口
用途:
- 根据 `evidenceId``watermarkedOssId` 生成“拟修复水印图”的临时预览
- 不落库,不覆盖 `sys_oss`
### 8.3 单条修复接口
用途:
- 对单条异常记录执行正式修复
- 修复成功后覆盖原 `sys_oss`
### 8.4 批量修复任务接口
用途:
- 创建批量修复任务
- 入参必须是前端勾选确认后的记录 ID 列表,而不是只有筛选条件
- 后台异步执行
- 支持查询进度、成功数、失败数、失败原因
### 8.5 操作日志接口
用途:
- 记录谁在什么时间修复了哪条记录
- 保留修复前后对象信息,便于追溯
## 9. 操作审计与回滚建议
### 9.1 建议记录的审计信息
每次修复建议记录:
- 操作人
- 操作时间
- `evidenceId`
- `watermarkedOssId`
-`file_name`
-`file_name`
-`url`
-`url`
-`ext1`
-`ext1`
- 修复结果
- 错误信息
### 9.2 回滚方式
由于要求保持原 `oss_id` 不变,回滚方案建议也按“对象替换”处理:
1. 修复前先记录旧对象信息。
2. 如修复后发现异常,可重新把旧对象重新上传或恢复。
3. 再次覆盖同一个 `sys_oss` 记录,使其回到修复前状态。
也就是说,回滚不是改回旧 `oss_id`,而是把同一个 `oss_id` 再次指回旧对象。
## 10. 推荐实施顺序
建议按以下顺序落地:
1. 先完成线上兜底,阻断新增坏数据。
2. 再开发后台可视化补救页面和补救接口。
3. 先选少量样本做单条修复验证。
4. 确认预览、下载、页面展示全部正常后,再进行批量修复。
5. 批量修复完成后,统一清理被替换掉的坏 OSS 对象。
## 11. 最终结论
本问题的正确处理方式是:
- 线上新增场景:后端不再信任原始上传后缀,水印图统一输出为 `jpg/jpeg`
- 历史补救场景:以 `HotPhotoEvidence` 为主表重建水印图,但不能改 `oss_id`,必须覆盖原坏水印图对应的 `sys_oss` 记录。
- 落地形式:优先做成后台管理可视化能力,支持异常筛选、预览、单条修复、批量修复、日志追踪。
该方案既能快速止血,也能在不扩散引用风险的前提下,稳妥完成历史数据修复。

View File

@@ -0,0 +1,63 @@
```sql
-- 传入你的 taskId
set @taskId = 'f5e730e3bfaf57ce00af7c0f7228958a';
start transaction;
-- 1. 归档到历史表
insert into sys_flow_task_his (his_id,
task_id,
instance_id,
node_code,
node_name,
approver_id,
status,
comment,
audit_time,
company_id,
is_read,
create_by,
create_time,
update_by,
update_time)
select replace(uuid(), '-', '') as his_id,
t.task_id,
t.instance_id,
t.node_code,
t.node_name,
t.approver_id,
case
when i.status = 1 then 1
when i.status in (2, 9) then 2
else 2
end as status,
'手工归档待办' as comment,
coalesce(i.finish_time, t.update_time, now()) as audit_time,
t.company_id,
t.is_read,
t.create_by,
t.create_time,
t.update_by,
t.update_time
from sys_flow_task t
left join sys_flow_instance i
on i.instance_id = t.instance_id
where t.task_id = @taskId
and not exists (select 1
from sys_flow_task_his th
where th.task_id = t.task_id
and th.instance_id = t.instance_id);
-- 2. 删除当前待办
delete
from sys_flow_task
where task_id = @taskId
and exists (select 1
from sys_flow_task_his th
where th.task_id = @taskId
and th.instance_id = sys_flow_task.instance_id);
commit;
```

View File

@@ -0,0 +1,14 @@
ALTER TABLE hot_vehicle_three_inspect
ADD COLUMN inspect_no varchar(32) NULL COMMENT '三检记录编号',
ADD COLUMN inspect_location varchar(255) NULL COMMENT '检查地点';
UPDATE hot_vehicle_three_inspect t
LEFT JOIN hot_vehicle v ON v.id = t.vehicle_id
SET t.inspect_no = CONCAT(
'SJ',
DATE_FORMAT(COALESCE(t.inspect_time, t.create_time), '%Y%m%d'),
RIGHT(COALESCE(v.plate_number, '000'), 3),
LPAD(MOD(COALESCE(t.inspect_id, t.id), 10000), 4, '0')
)
WHERE (t.inspect_no IS NULL OR t.inspect_no = '')
AND t.is_deleted = 0;

View File

@@ -0,0 +1,138 @@
-- 存量隐患排查数据迁移为“审核不通过”状态,并补写驳回历史
-- 使用前请先确认:
-- 1. 已执行 update_hidden_danger_inspection_reject_history.sql
-- 2. 首次执行前建议备份 hot_hidden_danger_inspection 与 hot_hidden_danger_inspection_audit_history
-- 3. 本脚本默认将所有未删除的老数据统一初始化为第 1 次驳回
START TRANSACTION;
-- 1. 将主表老数据统一调整为驳回状态
UPDATE `hot_hidden_danger_inspection`
SET `submit_version` = IFNULL(NULLIF(`submit_version`, 0), 1),
`audit_result` = 'REJECT',
`reject_count` = CASE
WHEN `reject_count` IS NULL OR `reject_count` = 0 THEN 1
ELSE `reject_count`
END,
`last_reject_reason` = CASE
WHEN `last_reject_reason` IS NULL OR `last_reject_reason` = '' THEN '历史数据初始化为审核不通过,请驾驶员重新编辑后提交'
ELSE `last_reject_reason`
END,
`last_reject_time` = COALESCE(`last_reject_time`, `audit_date`, `update_time`, `create_time`, NOW()),
`last_reject_by_id` = COALESCE(`last_reject_by_id`, `approver_id`),
`last_reject_by_name` = COALESCE(NULLIF(`last_reject_by_name`, ''), `approver_name`),
`flow_status` = 'REJECTED',
`instance_id` = NULL,
`status` = 10
WHERE `is_deleted` = 0;
-- 2. 将老数据写入驳回历史表
-- 说明:
-- - 每条主单补 1 条“第 1 次驳回”历史
-- - 已存在 inspection_id + audit_round=1 的记录时不会重复插入
SET @history_max_id := (SELECT IFNULL(MAX(`id`), 0) FROM `hot_hidden_danger_inspection_audit_history`);
INSERT INTO `hot_hidden_danger_inspection_audit_history` (
`id`,
`inspection_id`,
`company_id`,
`plan_id`,
`project_name`,
`project_id`,
`project_type`,
`submit_version`,
`audit_round`,
`audit_result`,
`reject_reason`,
`audit_date`,
`approver_id`,
`approver_name`,
`approver_sign_img_url`,
`audit_has_danger`,
`submit_snapshot_json`,
`check_content_json`,
`danger_desc`,
`attachment_url`,
`sign_img_url`,
`checker_id`,
`checker_name`,
`check_date`,
`create_dept`,
`create_by`,
`create_by_name`,
`create_time`,
`update_by`,
`update_by_name`,
`update_time`,
`is_deleted`,
`remark`
)
SELECT
(@history_max_id := @history_max_id + 1) AS `id`,
i.`id` AS `inspection_id`,
i.`company_id`,
i.`plan_id`,
i.`project_name`,
i.`project_id`,
i.`project_type`,
IFNULL(NULLIF(i.`submit_version`, 0), 1) AS `submit_version`,
1 AS `audit_round`,
'REJECT' AS `audit_result`,
COALESCE(NULLIF(i.`last_reject_reason`, ''), '历史数据初始化为审核不通过,请驾驶员重新编辑后提交') AS `reject_reason`,
COALESCE(i.`last_reject_time`, i.`audit_date`, i.`update_time`, i.`create_time`, NOW()) AS `audit_date`,
COALESCE(i.`last_reject_by_id`, i.`approver_id`) AS `approver_id`,
COALESCE(NULLIF(i.`last_reject_by_name`, ''), i.`approver_name`) AS `approver_name`,
i.`approver_sign_img_url`,
i.`audit_has_danger`,
JSON_OBJECT(
'inspectionId', i.`id`,
'companyId', i.`company_id`,
'planId', i.`plan_id`,
'projectName', i.`project_name`,
'projectId', i.`project_id`,
'projectType', i.`project_type`,
'submitVersion', IFNULL(NULLIF(i.`submit_version`, 0), 1),
'checkContentJson', i.`check_content_json`,
'dangerDesc', i.`danger_desc`,
'attachmentUrl', i.`attachment_url`,
'signImgUrl', i.`sign_img_url`,
'checkerId', i.`checker_id`,
'checkerName', i.`checker_name`,
'checkDate', i.`check_date`
) AS `submit_snapshot_json`,
i.`check_content_json`,
i.`danger_desc`,
i.`attachment_url`,
i.`sign_img_url`,
i.`checker_id`,
i.`checker_name`,
i.`check_date`,
i.`create_dept`,
COALESCE(i.`update_by`, i.`create_by`) AS `create_by`,
NULL AS `create_by_name`,
COALESCE(i.`update_time`, i.`create_time`, NOW()) AS `create_time`,
i.`update_by`,
NULL AS `update_by_name`,
i.`update_time`,
0 AS `is_deleted`,
'历史数据初始化补录' AS `remark`
FROM `hot_hidden_danger_inspection` i
WHERE i.`is_deleted` = 0
AND NOT EXISTS (
SELECT 1
FROM `hot_hidden_danger_inspection_audit_history` h
WHERE h.`inspection_id` = i.`id`
AND h.`audit_round` = 1
AND h.`is_deleted` = 0
);
COMMIT;
-- 校验SQL
-- SELECT COUNT(*) FROM hot_hidden_danger_inspection WHERE is_deleted = 0 AND audit_result = 'REJECT' AND status = 10;
-- SELECT COUNT(*) FROM hot_hidden_danger_inspection_audit_history WHERE is_deleted = 0;
update hot_hidden_danger_inspection set status = 1
update hot_hidden_danger_inspection set status = 10 where approver_id is not null

View File

@@ -0,0 +1,49 @@
ALTER TABLE `hot_hidden_danger_inspection`
ADD COLUMN `submit_version` bigint NULL DEFAULT 0 COMMENT '提交版本号' AFTER `flow_status`,
ADD COLUMN `audit_result` varchar(16) NULL COMMENT '审核结果PASS=通过 REJECT=驳回)' AFTER `submit_version`,
ADD COLUMN `reject_count` bigint NULL DEFAULT 0 COMMENT '驳回次数' AFTER `audit_result`,
ADD COLUMN `last_reject_reason` varchar(500) NULL COMMENT '最近一次驳回原因' AFTER `reject_count`,
ADD COLUMN `last_reject_time` datetime NULL COMMENT '最近一次驳回时间' AFTER `last_reject_reason`,
ADD COLUMN `last_reject_by_id` bigint NULL COMMENT '最近一次驳回人ID' AFTER `last_reject_time`,
ADD COLUMN `last_reject_by_name` varchar(64) NULL COMMENT '最近一次驳回人姓名' AFTER `last_reject_by_id`;
CREATE TABLE `hot_hidden_danger_inspection_audit_history` (
`id` bigint NOT NULL COMMENT '主键',
`inspection_id` bigint NOT NULL COMMENT '隐患排查主单ID',
`company_id` bigint DEFAULT NULL COMMENT '公司ID',
`plan_id` bigint DEFAULT NULL COMMENT '计划ID',
`project_name` varchar(255) DEFAULT NULL COMMENT '排查项目',
`project_id` varchar(64) DEFAULT NULL COMMENT '排查项目ID',
`project_type` bigint DEFAULT NULL COMMENT '排查类型1=公司 2=车辆 3=人员)',
`submit_version` bigint DEFAULT NULL COMMENT '提交版本号',
`audit_round` bigint DEFAULT NULL COMMENT '审核轮次',
`audit_result` varchar(16) DEFAULT NULL COMMENT '审核结果REJECT',
`reject_reason` varchar(500) DEFAULT NULL COMMENT '驳回原因',
`audit_date` datetime DEFAULT NULL COMMENT '审核日期',
`approver_id` bigint DEFAULT NULL COMMENT '审核人ID',
`approver_name` varchar(64) DEFAULT NULL COMMENT '审核人姓名',
`approver_sign_img_url` varchar(500) DEFAULT NULL COMMENT '审核人签名图片',
`audit_has_danger` bigint DEFAULT NULL COMMENT '审核时是否存在隐患',
`submit_snapshot_json` longtext COMMENT '当次提交快照JSON',
`check_content_json` longtext COMMENT '排查内容JSON',
`danger_desc` varchar(500) DEFAULT NULL COMMENT '隐患描述',
`attachment_url` varchar(2000) DEFAULT NULL COMMENT '附件URL',
`sign_img_url` varchar(500) DEFAULT NULL COMMENT '排查人签名图片',
`checker_id` varchar(64) DEFAULT NULL COMMENT '排查人ID',
`checker_name` varchar(64) DEFAULT NULL COMMENT '排查人姓名',
`check_date` datetime DEFAULT NULL COMMENT '排查日期',
`create_dept` bigint DEFAULT NULL COMMENT '创建部门',
`create_by` bigint DEFAULT NULL COMMENT '创建者',
`create_by_name` varchar(64) DEFAULT NULL COMMENT '创建者名称',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新者',
`update_by_name` varchar(64) DEFAULT NULL COMMENT '更新者名称',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` bigint DEFAULT 0 COMMENT '0=正常,1=已删除',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `idx_hdiah_inspection_id` (`inspection_id`),
KEY `idx_hdiah_company_id` (`company_id`),
KEY `idx_hdiah_audit_round` (`audit_round`),
KEY `idx_hdiah_is_deleted` (`is_deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='隐患排查审核驳回历史表';

View File

@@ -61,7 +61,7 @@ public final class DriverLoginContextHelper {
lqw.eq(HotDriver::getId, userId);
lqw.eq(HotDriver::getStatus, 1L);
lqw.eq(HotDriver::getAuditStatus, 1);
lqw.orderByDesc(HotDriver::getCreateTime);
lqw.orderByDesc(HotDriver::getCreateTime, HotDriver::getId);
lqw.last("limit 1");
HotDriverMapper hotDriverMapper = SpringUtils.getBean(HotDriverMapper.class);
HotDriver driver = hotDriverMapper.selectOne(lqw);

View File

@@ -2,9 +2,22 @@ package com.hotwj.platform.common.photoEvidence.mapper;
import com.hotwj.platform.common.photoEvidence.domain.HotPhotoEvidence;
import com.hotwj.platform.common.photoEvidence.domain.vo.PhotoEvidenceVo;
import com.hotwj.platform.system.maintenance.domain.bo.PhotoEvidenceRepairQueryBo;
import com.hotwj.platform.system.maintenance.domain.vo.PhotoEvidenceRepairCandidateVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import java.util.List;
@Mapper
public interface HotPhotoEvidenceMapper extends BaseMapperPlus<HotPhotoEvidence, PhotoEvidenceVo> {
long countRepairCandidates(@Param("query") PhotoEvidenceRepairQueryBo query);
List<PhotoEvidenceRepairCandidateVo> selectRepairCandidates(@Param("query") PhotoEvidenceRepairQueryBo query,
@Param("offset") long offset,
@Param("pageSize") long pageSize);
PhotoEvidenceRepairCandidateVo selectRepairCandidateByEvidenceId(@Param("evidenceId") Long evidenceId);
}

View File

@@ -6,6 +6,7 @@ import com.hotwj.platform.common.photoEvidence.domain.bo.PhotoEvidenceCreateBo;
import com.hotwj.platform.common.photoEvidence.domain.vo.PhotoEvidenceVo;
import com.hotwj.platform.common.photoEvidence.mapper.HotPhotoEvidenceMapper;
import com.hotwj.platform.common.photoEvidence.service.IPhotoEvidenceService;
import com.hotwj.platform.common.photoEvidence.support.PhotoEvidenceWatermarkSupport;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.enums.FormatsType;
@@ -22,16 +23,12 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Slf4j
@Service
@@ -43,6 +40,9 @@ public class PhotoEvidenceServiceImpl implements IPhotoEvidenceService {
@Resource
private ISysOssService ossService;
@Resource
private PhotoEvidenceWatermarkSupport watermarkSupport;
@Value("${map.gd.key:}")
private String gdKey;
@@ -129,30 +129,19 @@ public class PhotoEvidenceServiceImpl implements IPhotoEvidenceService {
Date captureTimeClient,
String address) {
byte[] originalBytes = downloadOssFile(originalOss);
BufferedImage originalImage = readImage(originalBytes);
BufferedImage watermarked = addWatermark(originalImage, captureTimeClient, address);
File tempFile = null;
PhotoEvidenceWatermarkSupport.RenderedWatermark renderedWatermark = null;
try {
String suffix = originalOss.getFileSuffix();
if (StringUtils.isBlank(suffix)) {
suffix = "jpg";
}
if (suffix.startsWith(".")) {
suffix = suffix.substring(1);
}
tempFile = File.createTempFile("photo_evidence_", "." + suffix);
try (OutputStream os = new FileOutputStream(tempFile)) {
ImageIO.write(watermarked, suffix, os);
}
return ossService.upload(tempFile).getOssId();
} catch (IOException e) {
throw new ServiceException("水印图片生成失败");
renderedWatermark = watermarkSupport.renderToTempFile(originalBytes, captureTimeClient, address);
OssClient storage = OssFactory.instance();
String objectKey = storage.getPath("photo-evidence", PhotoEvidenceWatermarkSupport.OUTPUT_SUFFIX);
return ossService.upload(
renderedWatermark.getFile(),
objectKey,
PhotoEvidenceWatermarkSupport.OUTPUT_CONTENT_TYPE
).getOssId();
} finally {
if (tempFile != null && tempFile.exists()) {
if (!tempFile.delete()) {
log.warn("临时文件删除失败: {}", tempFile.getAbsolutePath());
}
if (renderedWatermark != null) {
watermarkSupport.deleteQuietly(renderedWatermark.getFile());
}
}
}
@@ -167,124 +156,6 @@ public class PhotoEvidenceServiceImpl implements IPhotoEvidenceService {
}
}
private BufferedImage readImage(byte[] bytes) {
try (InputStream is = new ByteArrayInputStream(bytes)) {
BufferedImage image = ImageIO.read(is);
if (image == null) {
String hex = "";
if (bytes.length >= 4) {
hex = String.format("%02X%02X%02X%02X", bytes[0], bytes[1], bytes[2], bytes[3]);
}
log.error("ImageIO.read returned null. Header: {}", hex);
throw new ServiceException("原始图片格式不支持 (Header: " + hex + ")");
}
return image;
} catch (IOException e) {
log.error("图片解析失败", e);
throw new ServiceException("原始图片解析失败: " + e.getMessage());
}
}
private BufferedImage addWatermark(BufferedImage original,
Date captureTimeClient,
String address) {
int width = original.getWidth();
int height = original.getHeight();
// 强制最小画布宽度,防止文字区域过窄
int minWidth = 600;
if (width < minWidth) {
int newWidth = minWidth;
int newHeight = (int) Math.round((double) height * newWidth / width);
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D gResize = resized.createGraphics();
gResize.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
gResize.drawImage(original, 0, 0, newWidth, newHeight, null);
gResize.dispose();
original = resized;
width = newWidth;
height = newHeight;
}
// 字体大小随宽度自适应(设置最小值)
int fontSize = Math.max(20, width / 30);
int padding = Math.max(fontSize, 16);
String timeText = DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, captureTimeClient);
String addressText = "地址:" + (address != null ? address : "");
// 构建字体并测量
Font font;
try (InputStream is = getClass().getResourceAsStream("/fonts/simsun.ttc")) {
if (is == null) {
font = new Font("SansSerif", Font.PLAIN, fontSize);
} else {
font = Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(Font.PLAIN, fontSize);
}
} catch (Exception e) {
log.warn("Failed to load simsun font, fallback to SansSerif", e);
font = new Font("SansSerif", Font.PLAIN, fontSize);
}
// 使用临时图形上下文计算文字宽度与行高
BufferedImage tmp = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
Graphics2D gMeasure = tmp.createGraphics();
gMeasure.setFont(font);
FontMetrics fm = gMeasure.getFontMetrics();
int contentWidth = Math.max(1, width - padding * 2);
// 构造文本行:第一行时间,后续为地址按宽度换行
List<String> lines = new ArrayList<>();
lines.add(timeText);
if (fm.stringWidth(addressText) <= contentWidth) {
lines.add(addressText);
} else {
StringBuilder current = new StringBuilder();
for (char c : addressText.toCharArray()) {
if (fm.stringWidth(current.toString() + c) > contentWidth) {
lines.add(current.toString());
current = new StringBuilder();
}
current.append(c);
}
if (current.length() > 0) {
lines.add(current.toString());
}
}
int lineHeight = fm.getHeight() + fontSize / 4;
gMeasure.dispose();
// 计算白条高度,确保所有行完整显示
int barHeight = Math.max(lineHeight + padding * 2, lines.size() * lineHeight + padding * 2);
// 输出结果图
BufferedImage result = new BufferedImage(width, height + barHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = result.createGraphics();
try {
// 绘制原图
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(original, 0, 0, null);
// 绘制底部白条
g.setColor(Color.WHITE);
g.fillRect(0, height, width, barHeight);
// 绘制文本
g.setFont(font);
g.setColor(new Color(51, 51, 51));
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int y = height + padding + fm.getAscent();
for (String line : lines) {
g.drawString(line, padding, y);
y += lineHeight;
}
return result;
} finally {
g.dispose();
}
}
private String reverseGeocode(Double latitude, Double longitude) {
if (latitude == null || longitude == null) {
return null;

View File

@@ -0,0 +1,209 @@
package com.hotwj.platform.common.photoEvidence.support;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.DateUtils;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 照片取证水印图统一生成组件。
* 无论原图文件名或后缀是否异常,系统产出的水印图都固定为 jpg/jpeg。
*/
@Slf4j
@Component
public class PhotoEvidenceWatermarkSupport {
public static final String OUTPUT_FORMAT = "jpeg";
public static final String OUTPUT_SUFFIX = ".jpg";
public static final String OUTPUT_CONTENT_TYPE = "image/jpeg";
public RenderedWatermark renderToTempFile(byte[] originalBytes, Date captureTimeClient, String address) {
BufferedImage watermarked = renderImage(originalBytes, captureTimeClient, address);
File tempFile = null;
try {
tempFile = File.createTempFile("photo_evidence_", OUTPUT_SUFFIX);
boolean writeSuccess;
try (OutputStream os = new FileOutputStream(tempFile)) {
writeSuccess = ImageIO.write(watermarked, OUTPUT_FORMAT, os);
}
validateWriteResult(writeSuccess, tempFile);
return new RenderedWatermark(tempFile, tempFile.length());
} catch (IOException ex) {
deleteQuietly(tempFile);
throw new ServiceException("水印图片生成失败: " + ex.getMessage());
}
}
public byte[] renderToBytes(byte[] originalBytes, Date captureTimeClient, String address) {
BufferedImage watermarked = renderImage(originalBytes, captureTimeClient, address);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
boolean writeSuccess = ImageIO.write(watermarked, OUTPUT_FORMAT, outputStream);
if (!writeSuccess || outputStream.size() <= 0) {
throw new ServiceException("水印图片生成失败");
}
return outputStream.toByteArray();
} catch (IOException ex) {
throw new ServiceException("水印图片生成失败: " + ex.getMessage());
}
}
public String buildStandardOriginalName(Long ossId) {
if (ossId == null) {
return "photo_evidence" + OUTPUT_SUFFIX;
}
return "photo_evidence_" + ossId + OUTPUT_SUFFIX;
}
private BufferedImage renderImage(byte[] originalBytes, Date captureTimeClient, String address) {
BufferedImage originalImage = readImage(originalBytes);
return addWatermark(originalImage, captureTimeClient, address);
}
private void validateWriteResult(boolean writeSuccess, File tempFile) {
if (!writeSuccess) {
throw new ServiceException("水印图片生成失败: 未找到可用的 JPEG 编码器");
}
if (tempFile == null || !tempFile.exists()) {
throw new ServiceException("水印图片生成失败: 临时文件不存在");
}
if (tempFile.length() <= 0) {
throw new ServiceException("水印图片生成失败: 临时文件为空");
}
}
private BufferedImage readImage(byte[] bytes) {
try (InputStream is = new ByteArrayInputStream(bytes)) {
BufferedImage image = ImageIO.read(is);
if (image == null) {
String hex = "";
if (bytes.length >= 4) {
hex = String.format("%02X%02X%02X%02X", bytes[0], bytes[1], bytes[2], bytes[3]);
}
log.error("ImageIO.read returned null. Header: {}", hex);
throw new ServiceException("原始图片格式不支持 (Header: " + hex + ")");
}
return image;
} catch (IOException e) {
log.error("图片解析失败", e);
throw new ServiceException("原始图片解析失败: " + e.getMessage());
}
}
private BufferedImage addWatermark(BufferedImage original, Date captureTimeClient, String address) {
int width = original.getWidth();
int height = original.getHeight();
int minWidth = 600;
if (width < minWidth) {
int newWidth = minWidth;
int newHeight = (int) Math.round((double) height * newWidth / width);
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D gResize = resized.createGraphics();
gResize.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
gResize.drawImage(original, 0, 0, newWidth, newHeight, null);
gResize.dispose();
original = resized;
width = newWidth;
height = newHeight;
}
int fontSize = Math.max(20, width / 30);
int padding = Math.max(fontSize, 16);
String timeText = DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, captureTimeClient);
String addressText = "地址:" + (address != null ? address : "");
Font font;
try (InputStream is = getClass().getResourceAsStream("/fonts/simsun.ttc")) {
if (is == null) {
font = new Font("SansSerif", Font.PLAIN, fontSize);
} else {
font = Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(Font.PLAIN, fontSize);
}
} catch (Exception e) {
log.warn("Failed to load simsun font, fallback to SansSerif", e);
font = new Font("SansSerif", Font.PLAIN, fontSize);
}
BufferedImage tmp = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
Graphics2D gMeasure = tmp.createGraphics();
gMeasure.setFont(font);
FontMetrics fm = gMeasure.getFontMetrics();
int contentWidth = Math.max(1, width - padding * 2);
List<String> lines = new ArrayList<>();
lines.add(timeText);
if (fm.stringWidth(addressText) <= contentWidth) {
lines.add(addressText);
} else {
StringBuilder current = new StringBuilder();
for (char c : addressText.toCharArray()) {
if (fm.stringWidth(current.toString() + c) > contentWidth) {
lines.add(current.toString());
current = new StringBuilder();
}
current.append(c);
}
if (current.length() > 0) {
lines.add(current.toString());
}
}
int lineHeight = fm.getHeight() + fontSize / 4;
gMeasure.dispose();
int barHeight = Math.max(lineHeight + padding * 2, lines.size() * lineHeight + padding * 2);
BufferedImage result = new BufferedImage(width, height + barHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = result.createGraphics();
try {
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(original, 0, 0, null);
g.setColor(Color.WHITE);
g.fillRect(0, height, width, barHeight);
g.setFont(font);
g.setColor(new Color(51, 51, 51));
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int y = height + padding + fm.getAscent();
for (String line : lines) {
g.drawString(line, padding, y);
y += lineHeight;
}
return result;
} finally {
g.dispose();
}
}
public void deleteQuietly(File file) {
if (file == null || !file.exists()) {
return;
}
if (!file.delete()) {
log.warn("临时文件删除失败: {}", file.getAbsolutePath());
}
}
@Getter
public static class RenderedWatermark {
private final File file;
private final long fileSize;
public RenderedWatermark(File file, long fileSize) {
this.file = file;
this.fileSize = fileSize;
}
}
}

View File

@@ -40,12 +40,15 @@ public class HiddenDangerCheckCallback implements IFlowCallback {
HotHiddenDangerInspection inspection = inspectMapper.selectById(id);
if (inspection != null) {
if (success) {
inspection.setStatus(3L);
inspection.setFlowStatus("APPROVED");
inspection.setStatus(inspection.getAuditHasDanger() != null && inspection.getAuditHasDanger() == 1L ? 6L : 3L);
} else {
inspection.setStatus(6L);
inspection.setFlowStatus("REJECTED");
inspection.setStatus(10L);
inspection.setInstanceId(null);
log.info("隐患排查流程被驳回或非正常结束: {}", businessId);
}
if (inspection.getAuditHasDanger() != null && inspection.getAuditHasDanger() == 1L) {
if (success && inspection.getAuditHasDanger() != null && inspection.getAuditHasDanger() == 1L) {
createHiddenDangerFlow(inspection);
}
inspectMapper.updateById(inspection);

View File

@@ -37,25 +37,53 @@ public class HiddenDangerCheckStrategy implements IFlowStrategy {
@Override
public FlowNextDto next(SysFlowInstance instance, String currentNodeCode) {
// 简单流程:只有一级审批,审批通过即结束
if ("NODE_CHECK_EDIT".equals(currentNodeCode)) {
return buildAuditNode(instance.getBusinessId());
}
// 审核通过即结束
return null;
}
@Override
public FlowNextDto reject(SysFlowInstance instance, String currentNodeCode) {
if (!"NODE_CHECK_AUDIT".equals(currentNodeCode)) {
return null;
}
InspectionNodeMeta meta = getInspectionNodeMeta(instance.getBusinessId());
String editApproverId = meta.inspection != null && meta.inspection.getCheckerId() != null
? meta.inspection.getCheckerId() : instance.getInitiatorId();
return buildEditNode(instance.getBusinessId(), editApproverId);
}
@Override
public FlowNextDto getInitialNode(String businessId) {
return getNextNode(businessId);
return buildAuditNode(businessId);
}
private FlowNextDto getNextNode(String id) {
private FlowNextDto buildAuditNode(String id) {
InspectionNodeMeta meta = getInspectionNodeMeta(id);
String approverId = meta.inspection != null && meta.inspection.getApproverId() != null
? String.valueOf(meta.inspection.getApproverId()) : null;
String nodeName = String.format("%s%s审核", meta.planName, meta.inspectItemName);
return new FlowNextDto("NODE_CHECK_AUDIT", nodeName, approverId);
}
private FlowNextDto buildEditNode(String id, String approverId) {
InspectionNodeMeta meta = getInspectionNodeMeta(id);
String nodeName = String.format("%s%s处理", meta.planName, meta.inspectItemName);
return new FlowNextDto("NODE_CHECK_EDIT", nodeName, approverId);
}
private InspectionNodeMeta getInspectionNodeMeta(String id) {
// 待办名称规则:(排查计划名称)(排查项名称)审核
String inspectItemName = "未知排查项";
String planName = "未知计划";
HotHiddenDangerInspection inspection = null;
try {
if (id != null) {
HotHiddenDangerInspection inspection = inspectMapper.selectById(id);
inspection = inspectMapper.selectById(id);
if (inspection != null) {
// 处理排查项名称
if (inspection.getProjectName() != null) {
@@ -80,8 +108,9 @@ public class HiddenDangerCheckStrategy implements IFlowStrategy {
} catch (Exception e) {
// 忽略异常,使用默认值
}
return new InspectionNodeMeta(inspection, inspectItemName, planName);
}
String nodeName = String.format("%s%s审核", planName, inspectItemName);
return new FlowNextDto("NODE_CHECK_AUDIT", nodeName, null);
private record InspectionNodeMeta(HotHiddenDangerInspection inspection, String inspectItemName, String planName) {
}
}

View File

@@ -73,7 +73,7 @@ public class HotRiskLevelConfigServiceImpl implements IHotRiskLevelConfigService
private LambdaQueryWrapper<HotRiskLevelConfig> buildQueryWrapper(HotRiskLevelConfigBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotRiskLevelConfig> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotRiskLevelConfig::getLevelValue);
lqw.orderByDesc(HotRiskLevelConfig::getLevelValue, HotRiskLevelConfig::getId);
lqw.eq(bo.getLevelValue() != null, HotRiskLevelConfig::getLevelValue, bo.getLevelValue());
lqw.like(StringUtils.isNotBlank(bo.getLevelName()), HotRiskLevelConfig::getLevelName, bo.getLevelName());
lqw.eq(StringUtils.isNotBlank(bo.getDescription()), HotRiskLevelConfig::getDescription, bo.getDescription());

View File

@@ -1,5 +1,6 @@
package com.hotwj.platform.config.vehicleThreeInspect.callback;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hotwj.platform.config.vehicleThreeInspect.domain.HotVehicleThreeInspect;
import com.hotwj.platform.config.vehicleThreeInspect.mapper.HotVehicleThreeInspectMapper;
import com.hotwj.platform.flow.callback.IFlowCallback;
@@ -10,8 +11,8 @@ import com.hotwj.platform.securityManagement.hiddenDanger.domain.HotHiddenDanger
import com.hotwj.platform.securityManagement.hiddenDanger.domain.bo.HotHiddenDangerBo;
import com.hotwj.platform.securityManagement.hiddenDanger.mapper.HotHiddenDangerMapper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@@ -48,7 +49,7 @@ public class VehicleThreeInspectCallback implements IFlowCallback {
@Override
public void onProcessEnd(String instanceId, String businessId, String flowCode, boolean success) {
log.info("车辆三检流程结束回调: businessId={}, success={}", businessId, success);
log.info("车辆三检流程结束回调: instanceId={}, businessId={}, flowCode={}, success={}", instanceId, businessId, flowCode, success);
Long id = Long.parseLong(businessId);
// 去掉try catch防止事务无法正常回滚
HotVehicleThreeInspect inspect = inspectMapper.selectById(id);
@@ -74,45 +75,70 @@ public class VehicleThreeInspectCallback implements IFlowCallback {
* 创建隐患治理流程
*/
private void createHiddenDangerFlow(HotVehicleThreeInspect inspect) {
if (inspect.getHasHiddenDanger() != null && inspect.getHasHiddenDanger() == 1L) {
log.info("车辆三检存在隐患自动创建隐患治理流程业务ID: {}", inspect.getId());
HotHiddenDangerBo hiddenDangerBo = new HotHiddenDangerBo();
hiddenDangerBo.setCompanyId(inspect.getCompanyId());
hiddenDangerBo.setCheckTime(new Date());
hiddenDangerBo.setCheckerId(String.valueOf(inspect.getAuditorId()));
hiddenDangerBo.setCheckerName(inspect.getAuditorName());
hiddenDangerBo.setDescription(inspect.getAuditResult());
hiddenDangerBo.setEvidenceUrls(inspect.getAttachImgUrl());
hiddenDangerBo.setDangerNo("YH-" + System.currentTimeMillis() / 1000);
hiddenDangerBo.setSourceName("车辆");
hiddenDangerBo.setSourceId(String.valueOf(inspect.getId()));
hiddenDangerBo.setSourceType(2L);
hiddenDangerBo.setDataSource(2L);
hiddenDangerBo.setDangerName("车辆三检治理");
hiddenDangerBo.setStatus(0L);
HotHiddenDanger danger = MapstructUtils.convert(hiddenDangerBo, HotHiddenDanger.class);
hiddenDangerMapper.insert(danger);
// 启动隐患治理流程
// 发起人为审核人(发现隐患的人)
String initiator = String.valueOf(inspect.getAuditorId());
// 默认整改人也先设置为审核人,或者留空等待指派
String rectifierId = String.valueOf(inspect.getEvaluatorId());
String instanceId = flowService.startFlow(
"HIDDEN_DANGER_GOVERNANCE",
String.valueOf(danger.getId()),
inspect.getCompanyId(),
initiator,
rectifierId
);
danger.setInstanceId(instanceId);
danger.setEvaluatorId(inspect.getEvaluatorId());
danger.setFlowStatus("SUBMITTED");
hiddenDangerMapper.updateById(danger);
if (!Long.valueOf(1L).equals(inspect.getHasHiddenDanger())) {
log.info("车辆三检未标记隐患,跳过隐患治理创建: recordId={}, inspectId={}", inspect.getId(), inspect.getInspectId());
return;
}
String sourceId = String.valueOf(inspect.getInspectId() != null ? inspect.getInspectId() : inspect.getId());
log.info("车辆三检存在隐患,准备创建隐患治理流程: recordId={}, inspectId={}, sourceId={}, auditorId={}, evaluatorId={}",
inspect.getId(), inspect.getInspectId(), sourceId, inspect.getAuditorId(), inspect.getEvaluatorId());
HotHiddenDanger existingDanger = hiddenDangerMapper.selectOne(
Wrappers.<HotHiddenDanger>lambdaQuery()
.eq(HotHiddenDanger::getSourceType, 2L)
.eq(HotHiddenDanger::getDataSource, 2L)
.eq(HotHiddenDanger::getSourceId, sourceId)
.eq(HotHiddenDanger::getIsDeleted, 0L)
.last("LIMIT 1")
);
if (existingDanger != null) {
log.warn("车辆三检隐患治理已存在,跳过重复创建: recordId={}, inspectId={}, sourceId={}, existingDangerId={}, existingInstanceId={}",
inspect.getId(), inspect.getInspectId(), sourceId, existingDanger.getId(), existingDanger.getInstanceId());
return;
}
HotHiddenDangerBo hiddenDangerBo = new HotHiddenDangerBo();
hiddenDangerBo.setCompanyId(inspect.getCompanyId());
hiddenDangerBo.setCheckTime(new Date());
hiddenDangerBo.setCheckerId(String.valueOf(inspect.getAuditorId()));
hiddenDangerBo.setCheckerName(inspect.getAuditorName());
hiddenDangerBo.setDescription(StringUtils.defaultIfBlank(
StringUtils.trimToNull(inspect.getRejectReason()),
resolveReadableAuditResult(inspect.getAuditResult())
));
hiddenDangerBo.setEvidenceUrls(inspect.getAttachImgUrl());
hiddenDangerBo.setDangerNo("YH-" + System.currentTimeMillis() / 1000);
hiddenDangerBo.setSourceName("车辆");
hiddenDangerBo.setSourceId(sourceId);
hiddenDangerBo.setSourceType(2L);
hiddenDangerBo.setDataSource(2L);
hiddenDangerBo.setDangerName("车辆三检治理");
hiddenDangerBo.setStatus(0L);
HotHiddenDanger danger = MapstructUtils.convert(hiddenDangerBo, HotHiddenDanger.class);
hiddenDangerMapper.insert(danger);
log.info("车辆三检隐患治理数据已创建: recordId={}, inspectId={}, sourceId={}, dangerId={}",
inspect.getId(), inspect.getInspectId(), sourceId, danger.getId());
// 启动隐患治理流程
// 发起人为审核人(发现隐患的人)
String initiator = String.valueOf(inspect.getAuditorId());
// 默认整改人也先设置为审核人,或者留空等待指派
String rectifierId = String.valueOf(inspect.getEvaluatorId());
String instanceId = flowService.startFlow(
"HIDDEN_DANGER_GOVERNANCE",
String.valueOf(danger.getId()),
inspect.getCompanyId(),
initiator,
rectifierId
);
danger.setInstanceId(instanceId);
danger.setEvaluatorId(inspect.getEvaluatorId());
danger.setFlowStatus("SUBMITTED");
hiddenDangerMapper.updateById(danger);
log.info("车辆三检隐患治理流程已启动: recordId={}, inspectId={}, sourceId={}, dangerId={}, hiddenDangerInstanceId={}",
inspect.getId(), inspect.getInspectId(), sourceId, danger.getId(), instanceId);
}
private void sendDriverAuditNotification(HotVehicleThreeInspect inspect, boolean success) {
@@ -139,13 +165,27 @@ public class VehicleThreeInspectCallback implements IFlowCallback {
if (StringUtils.isNotBlank(rejectReason)) {
return "您的车辆三检【" + phaseName + "】未通过,不通过原因:" + rejectReason;
}
String auditResult = StringUtils.trimToEmpty(inspect.getAuditResult());
String auditResult = StringUtils.trimToEmpty(resolveReadableAuditResult(inspect.getAuditResult()));
if (StringUtils.isNotBlank(auditResult)) {
return "您的车辆三检【" + phaseName + "】未通过,审核意见:" + auditResult;
}
return "您的车辆三检【" + phaseName + "】未通过,请及时查看审核意见。";
}
private String resolveReadableAuditResult(String auditResult) {
String value = StringUtils.trimToNull(auditResult);
if (value == null) {
return null;
}
if ("1".equals(value)) {
return "通过";
}
if ("2".equals(value)) {
return "不通过";
}
return value;
}
private String getPhaseName(Long phase) {
if (phase == null) {
return "未知阶段";

View File

@@ -2,9 +2,11 @@ package com.hotwj.platform.config.vehicleThreeInspect.controller;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectBatchAuditBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectMultiVehicleBatchAuditBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectProcessVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectVehicleQueryBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectReceivedVehicleVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectSummaryVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectUnreceivedVehicleVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectVo;
import com.hotwj.platform.config.vehicleThreeInspect.service.IHotVehicleThreeInspectPrintService;
@@ -14,6 +16,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
@@ -94,6 +97,16 @@ public class HotVehicleThreeInspectController extends BaseController {
return R.ok(hotVehicleThreeInspectService.queryReceivedVehicles(bo));
}
/**
* 校验当前驾驶员是否可选择指定车辆
*/
@GetMapping("/checkSelectableVehicle")
@Operation(summary = "校验当前驾驶员是否可选择指定车辆")
public R<Boolean> checkSelectableVehicle(@RequestParam @NotBlank(message = "驾驶员ID不能为空") String driverId,
@RequestParam @NotBlank(message = "车辆ID不能为空") String vehicleId) {
return R.ok(hotVehicleThreeInspectService.checkSelectableVehicle(driverId, vehicleId));
}
/**
* 导出车辆三检列表
*/
@@ -202,4 +215,54 @@ public class HotVehicleThreeInspectController extends BaseController {
@PathVariable Long[] ids) {
return toAjax(hotVehicleThreeInspectService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 查询车辆三检聚合列表
*/
@GetMapping("/listSummary")
@Operation(summary = "分页查询车辆三检聚合列表")
public TableDataInfo<HotVehicleThreeInspectSummaryVo> listSummary(HotVehicleThreeInspectBo bo, PageQuery pageQuery) {
return hotVehicleThreeInspectService.querySummaryPageList(bo, pageQuery);
}
/**
* 查询当前人员可进行多车辆批量审核的车辆三检列表
*/
@GetMapping("/multiVehicleBatchAudit/list")
@Operation(summary = "分页查询当前人员可进行多车辆批量审核的车辆三检列表")
public TableDataInfo<HotVehicleThreeInspectSummaryVo> listMultiVehicleBatchAudit(HotVehicleThreeInspectVehicleQueryBo bo, PageQuery pageQuery) {
return hotVehicleThreeInspectService.queryBatchAuditVehiclePageList(bo, pageQuery);
}
/**
* 多车辆批量审核车辆三检
*/
@Log(title = "车辆三检多车辆批量审核", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PostMapping("/multiVehicleBatchAudit")
@Operation(summary = "多车辆批量审核车辆三检")
public R<Void> multiVehicleBatchAudit(@Validated @RequestBody HotVehicleThreeInspectMultiVehicleBatchAuditBo bo) {
hotVehicleThreeInspectService.multiVehicleBatchAudit(bo);
return R.ok();
}
/**
* 查询车辆三检历史记录列表
*/
@GetMapping("/history/list")
@Operation(summary = "分页查询车辆三检历史记录列表")
public TableDataInfo<HotVehicleThreeInspectSummaryVo> listHistory(HotVehicleThreeInspectBo bo, PageQuery pageQuery) {
return hotVehicleThreeInspectService.queryHistoryPageList(bo, pageQuery);
}
/**
* 查询车辆三检历史记录详情
*/
@GetMapping("/history/{inspectId}")
@Operation(summary = "查询车辆三检历史记录详情")
public R<HotVehicleThreeInspectSummaryVo> getHistoryDetail(@NotNull(message = "三检流程ID不能为空")
@Parameter(name = "inspectId", description = "三检流程ID", required = true, example = "1")
@PathVariable Long inspectId) {
return R.ok(hotVehicleThreeInspectService.queryHistoryDetail(inspectId));
}
}

View File

@@ -60,11 +60,21 @@ public class HotVehicleThreeInspect extends BaseEntity {
*/
private Long inspectId;
/**
* 三检记录编号
*/
private String inspectNo;
/**
* 检查时间
*/
private Date inspectTime;
/**
* 检查地点
*/
private String inspectLocation;
/**
* 审核人ID
*/

View File

@@ -63,12 +63,32 @@ public class HotVehicleThreeInspectBo extends BaseEntity {
*/
private Long inspectId;
/**
* 三检记录编号
*/
private String inspectNo;
/**
* 检查时间
*/
@NotNull(message = "检查时间不能为空", groups = {AddGroup.class, EditGroup.class})
private Date inspectTime;
/**
* 检查地点
*/
private String inspectLocation;
/**
* 纬度
*/
private Double latitude;
/**
* 经度
*/
private Double longitude;
/**
* 审核人ID
*/

View File

@@ -0,0 +1,57 @@
package com.hotwj.platform.config.vehicleThreeInspect.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* 车辆三检多车辆批量审核业务对象
*
* @author shihongwei
* @date 2026-06-17
*/
@Data
public class HotVehicleThreeInspectMultiVehicleBatchAuditBo {
/**
* 三检流程ID集合
*/
@NotEmpty(message = "三检流程ID不能为空")
private List<Long> inspectIds;
/**
* 是否存在隐患 0=否, 1=是
*/
@NotNull(message = "是否存在隐患不能为空")
private Long hasHiddenDanger;
/**
* 评估人ID
*/
private Long evaluatorId;
/**
* 审核时间
*/
private Date auditTime;
/**
* 审核结论
*/
private String auditResult;
/**
* 审核不通过原因
*/
private String rejectReason;
/**
* 审核人员签名图片url
*/
@NotBlank(message = "审核人员签名不能为空")
private String auditorSignImgUrl;
}

View File

@@ -13,50 +13,48 @@ public class HotVehicleThreeInspectPrintVo {
*/
private Long inspectId;
/**
* 三检记录编号
*/
private String inspectNo;
/**
* 车牌号
*/
private String plateNumber;
// --- 出车前 Phase 1 ---
/**
* 出车前-检查时间
* 检查人
*/
private String startPhaseTime;
/**
* 出车前-状态
*/
private String startPhaseStatus;
/**
* 出车前-隐患
*/
private String startPhaseHiddenDanger;
private String inspectorName;
// --- 行车中 Phase 2 ---
/**
* 行车中-检查时间
* 审核人
*/
private String runningPhaseTime;
/**
* 行车中-状态
*/
private String runningPhaseStatus;
/**
* 行车中-隐患
*/
private String runningPhaseHiddenDanger;
private String auditorName;
// --- 收车后 Phase 3 ---
/**
* 收车后-检查时间
* 检查开始时间
*/
private String endPhaseTime;
private String startTime;
/**
* 收车后-状态
* 检查结束时间
*/
private String endPhaseStatus;
private String endTime;
/**
* 收车后-隐患
* 出车前检查地点
*/
private String endPhaseHiddenDanger;
private String outInspectLocation;
/**
* 行车中检查地点
*/
private String drivingInspectLocation;
/**
* 收车后检查地点
*/
private String backInspectLocation;
}

View File

@@ -0,0 +1,130 @@
package com.hotwj.platform.config.vehicleThreeInspect.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 车辆三检聚合列表视图对象
*
* @author shihongwei
* @date 2026-06-01
*/
@Data
public class HotVehicleThreeInspectSummaryVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 三检流程ID
*/
private Long inspectId;
/**
* 三检记录编号
*/
private String inspectNo;
/**
* 公司ID
*/
private Long companyId;
/**
* 车辆ID
*/
private Long vehicleId;
/**
* 车牌号
*/
private String plateNumber;
/**
* 驾驶员ID
*/
private String driverId;
/**
* 驾驶员姓名
*/
private String driverName;
/**
* 检查人
*/
private String inspectorName;
/**
* 审核人
*/
private String auditorName;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
/**
* 检查时间展示
*/
private String inspectTime;
/**
* 完成状态
*/
private String completeStatus;
/**
* 是否已完成
*/
private Boolean completed;
/**
* 审核完成状态
*/
private String auditCompleteStatus;
/**
* 是否完成审核
*/
private Boolean auditCompleted;
/**
* 出车前检查地点
*/
private String outInspectLocation;
/**
* 行车中检查地点
*/
private String drivingInspectLocation;
/**
* 收车后检查地点
*/
private String backInspectLocation;
/**
* 出车检查
*/
private HotVehicleThreeInspectVo outCheck;
/**
* 行车检查
*/
private HotVehicleThreeInspectVo drivingCheck;
/**
* 收车检查
*/
private HotVehicleThreeInspectVo backCheck;
}

View File

@@ -66,12 +66,24 @@ public class HotVehicleThreeInspectVo implements Serializable {
*/
private Long inspectId;
/**
* 三检记录编号
*/
@ExcelProperty(value = "三检记录编号")
private String inspectNo;
/**
* 检查时间
*/
@ExcelProperty(value = "检查时间")
private Date inspectTime;
/**
* 检查地点
*/
@ExcelProperty(value = "检查地点")
private String inspectLocation;
/**
* 审核人ID
*/

View File

@@ -6,6 +6,7 @@ import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeIn
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectProcessVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectPrintVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectReceivedVehicleVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectSummaryVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectUnreceivedVehicleVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectVo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -44,6 +45,48 @@ public interface HotVehicleThreeInspectMapper extends BaseMapperPlus<HotVehicleT
@Param("bo") HotVehicleThreeInspectBo bo
);
/**
* 分页查询车辆三检聚合列表
*
* @param page 分页参数
* @param bo 查询条件
* @return 聚合分页列表
*/
Page<HotVehicleThreeInspectSummaryVo> querySummaryPageList(
Page<HotVehicleThreeInspectSummaryVo> page,
@Param("bo") HotVehicleThreeInspectBo bo
);
/**
* 分页查询当前人员可进行多车辆批量审核的车辆三检聚合列表
*
* @param page 分页参数
* @param bo 查询条件
* @param auditorId 当前审核人ID
* @return 聚合分页列表
*/
Page<HotVehicleThreeInspectSummaryVo> queryBatchAuditVehiclePageList(
Page<HotVehicleThreeInspectSummaryVo> page,
@Param("bo") HotVehicleThreeInspectVehicleQueryBo bo,
@Param("auditorId") Long auditorId
);
/**
* 按流程ID查询车辆三检聚合详情
*
* @param inspectId 流程ID
* @return 聚合详情
*/
HotVehicleThreeInspectSummaryVo querySummaryByInspectId(@Param("inspectId") Long inspectId);
/**
* 按流程ID集合查询三检明细
*
* @param inspectIds 流程ID集合
* @return 三检明细列表
*/
List<HotVehicleThreeInspectVo> querySummaryDetailList(@Param("inspectIds") List<Long> inspectIds);
/**
* 查询所有三检未收车的车辆列表
*

View File

@@ -2,9 +2,11 @@ package com.hotwj.platform.config.vehicleThreeInspect.service;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectBatchAuditBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectMultiVehicleBatchAuditBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectVehicleQueryBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectProcessVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectReceivedVehicleVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectSummaryVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectUnreceivedVehicleVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectVo;
import com.hotwj.platform.resourceManagement.vehicleManagement.domain.vo.HotVehicleVo;
@@ -72,6 +74,41 @@ public interface IHotVehicleThreeInspectService {
*/
TableDataInfo<HotVehicleThreeInspectProcessVo> queryProcessPageList(HotVehicleThreeInspectBo bo, PageQuery pageQuery);
/**
* 分页查询车辆三检聚合列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 车辆三检聚合分页列表
*/
TableDataInfo<HotVehicleThreeInspectSummaryVo> querySummaryPageList(HotVehicleThreeInspectBo bo, PageQuery pageQuery);
/**
* 分页查询当前人员可进行多车辆批量审核的三检流程列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 聚合分页列表
*/
TableDataInfo<HotVehicleThreeInspectSummaryVo> queryBatchAuditVehiclePageList(HotVehicleThreeInspectVehicleQueryBo bo, PageQuery pageQuery);
/**
* 查询车辆三检历史记录分页列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 历史记录分页列表
*/
TableDataInfo<HotVehicleThreeInspectSummaryVo> queryHistoryPageList(HotVehicleThreeInspectBo bo, PageQuery pageQuery);
/**
* 按三检流程ID查询历史详情
*
* @param inspectId 三检流程ID
* @return 历史详情
*/
HotVehicleThreeInspectSummaryVo queryHistoryDetail(Long inspectId);
/**
* 查询符合条件的车辆三检列表
*
@@ -114,6 +151,16 @@ public interface IHotVehicleThreeInspectService {
*/
void validateNoUnfinishedSwitchVehicleProcess(String vehicleId, String actionMessage);
/**
* 校验当前驾驶员是否可选择指定车辆。
* 若车辆未出车则直接返回true若车辆已出车且当次三检流程未完成且记录驾驶员不是当前驾驶员则抛出异常。
*
* @param driverId 当前驾驶员ID
* @param vehicleId 车辆ID
* @return 是否可选择
*/
boolean checkSelectableVehicle(String driverId, String vehicleId);
/**
* 修改车辆三检
*
@@ -144,4 +191,11 @@ public interface IHotVehicleThreeInspectService {
* @param bo 批量审核业务对象
*/
void batchAudit(HotVehicleThreeInspectBatchAuditBo bo);
/**
* 多车辆批量审核车辆三检
*
* @param bo 多车辆批量审核业务对象
*/
void multiVehicleBatchAudit(HotVehicleThreeInspectMultiVehicleBatchAuditBo bo);
}

View File

@@ -1,31 +1,35 @@
package com.hotwj.platform.config.vehicleThreeInspect.service.impl;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.hotwj.platform.common.helper.DriverLoginContextHelper;
import com.hotwj.platform.config.vehicleThreeInspect.domain.HotVehicleThreeInspect;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectBatchAuditBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectMultiVehicleBatchAuditBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.bo.HotVehicleThreeInspectVehicleQueryBo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectProcessVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectReceivedVehicleVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectUnreceivedVehicleVo;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.HotVehicleThreeInspectVo;
import com.hotwj.platform.config.vehicleThreeInspectConfig.domain.HotVehicleThreeInspectConfig;
import com.hotwj.platform.config.vehicleThreeInspectConfig.mapper.HotVehicleThreeInspectConfigMapper;
import com.hotwj.platform.config.vehicleThreeInspect.domain.vo.*;
import com.hotwj.platform.config.vehicleThreeInspect.mapper.HotVehicleThreeInspectMapper;
import com.hotwj.platform.config.vehicleThreeInspect.service.IHotVehicleThreeInspectService;
import com.hotwj.platform.config.vehicleThreeInspectConfig.domain.HotVehicleThreeInspectConfig;
import com.hotwj.platform.config.vehicleThreeInspectConfig.mapper.HotVehicleThreeInspectConfigMapper;
import com.hotwj.platform.flow.service.ISysFlowService;
import com.hotwj.platform.integration.ocr.SignatureVerifyService;
import com.hotwj.platform.noticeManagerment.systemNotification.domain.bo.HotSystemNotificationGroupBo;
import com.hotwj.platform.noticeManagerment.systemNotification.service.IHotSystemNotificationService;
import com.hotwj.platform.resourceManagement.vehicleManagement.domain.HotVehicle;
import com.hotwj.platform.resourceManagement.vehicleManagement.domain.vo.HotVehicleVo;
import com.hotwj.platform.resourceManagement.vehicleManagement.mapper.HotVehicleMapper;
import com.hotwj.platform.securityManagement.hiddenDanger.mapper.HotHiddenDangerMapper;
import com.hotwj.platform.securityManagement.vehicleInoutCheck.domain.HotVehicleInoutCheck;
import com.hotwj.platform.securityManagement.vehicleInoutCheck.mapper.HotVehicleInoutCheckMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -33,15 +37,16 @@ import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
/**
* 车辆三检Service业务层处理
@@ -57,11 +62,15 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
private final HotVehicleThreeInspectMapper baseMapper;
private final HotVehicleMapper hotVehicleMapper;
private final HotHiddenDangerMapper hotHiddenDangerMapper;
private final HotVehicleInoutCheckMapper hotVehicleInoutCheckMapper;
private final ISysFlowService flowService;
private final IHotSystemNotificationService notificationService;
private final SignatureVerifyService signatureVerifyService;
private final HotVehicleThreeInspectConfigMapper hotVehicleThreeInspectConfigMapper;
@Value("${map.gd.key:}")
private String gdKey;
/**
* 查询可进行三检的车辆列表(无历史记录或上一轮已完结)
*
@@ -121,6 +130,27 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
}
}
@Override
public boolean checkSelectableVehicle(String driverId, String vehicleId) {
String trimmedDriverId = StringUtils.trimToNull(driverId);
String trimmedVehicleId = StringUtils.trimToNull(vehicleId);
if (StringUtils.isBlank(trimmedDriverId)) {
throw new ServiceException("驾驶员ID不能为空");
}
Long parsedVehicleId = parseVehicleId(trimmedVehicleId);
if (parsedVehicleId == null) {
return true;
}
HotVehicleThreeInspect latestInspect = selectLatestInspect(parsedVehicleId);
if (latestInspect == null || Objects.equals(latestInspect.getInspectPhase(), 3L)) {
return true;
}
if (StringUtils.equals(trimmedDriverId, StringUtils.trimToEmpty(latestInspect.getDriverId()))) {
return true;
}
throw new ServiceException(buildVehicleOccupiedMessage(parsedVehicleId));
}
/**
* 查询车辆三检
*
@@ -172,6 +202,54 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
return TableDataInfo.build(result);
}
/**
* 分页查询车辆三检聚合列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 车辆三检聚合分页列表
*/
@Override
public TableDataInfo<HotVehicleThreeInspectSummaryVo> querySummaryPageList(HotVehicleThreeInspectBo bo, PageQuery pageQuery) {
Page<HotVehicleThreeInspectSummaryVo> result = baseMapper.querySummaryPageList(pageQuery.build(), bo);
enrichSummaryList(result.getRecords());
return TableDataInfo.build(result);
}
@Override
public TableDataInfo<HotVehicleThreeInspectSummaryVo> queryBatchAuditVehiclePageList(HotVehicleThreeInspectVehicleQueryBo bo, PageQuery pageQuery) {
Long currentAuditorId = resolveCurrentAuditorId();
LoginUser loginUser = LoginHelper.getLoginUser();
if (bo == null) {
bo = new HotVehicleThreeInspectVehicleQueryBo();
}
if (bo.getCompanyId() == null && loginUser != null) {
bo.setCompanyId(loginUser.getCompanyId());
}
Page<HotVehicleThreeInspectSummaryVo> result = baseMapper.queryBatchAuditVehiclePageList(pageQuery.build(), bo, currentAuditorId);
return TableDataInfo.build(result);
}
@Override
public TableDataInfo<HotVehicleThreeInspectSummaryVo> queryHistoryPageList(HotVehicleThreeInspectBo bo, PageQuery pageQuery) {
return querySummaryPageList(bo, pageQuery);
}
@Override
public HotVehicleThreeInspectSummaryVo queryHistoryDetail(Long inspectId) {
HotVehicleThreeInspectSummaryVo summary = baseMapper.querySummaryByInspectId(inspectId);
if (summary == null) {
throw new ServiceException("车辆三检历史记录不存在");
}
List<HotVehicleThreeInspectVo> detailList = baseMapper.querySummaryDetailList(Collections.singletonList(inspectId));
fillTaskId(detailList);
for (HotVehicleThreeInspectVo detail : detailList) {
attachPhaseDetail(summary, detail);
}
refreshSummary(summary);
return summary;
}
/**
* 查询符合条件的车辆三检列表
*
@@ -184,10 +262,56 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
return baseMapper.selectVoList(lqw);
}
private void attachPhaseDetail(HotVehicleThreeInspectSummaryVo summary, HotVehicleThreeInspectVo detail) {
if (summary == null || detail == null || detail.getInspectPhase() == null) {
return;
}
if (StringUtils.isBlank(summary.getInspectNo()) && StringUtils.isNotBlank(detail.getInspectNo())) {
summary.setInspectNo(detail.getInspectNo());
}
if (StringUtils.isBlank(summary.getInspectorName()) && StringUtils.isNotBlank(detail.getDriverName())) {
summary.setInspectorName(detail.getDriverName());
}
if (StringUtils.isBlank(summary.getAuditorName()) && StringUtils.isNotBlank(detail.getAuditorName())) {
summary.setAuditorName(detail.getAuditorName());
}
Long inspectPhase = detail.getInspectPhase();
if (Objects.equals(inspectPhase, 1L)) {
if (summary.getOutCheck() == null) {
summary.setOutCheck(detail);
summary.setOutInspectLocation(detail.getInspectLocation());
}
return;
}
if (Objects.equals(inspectPhase, 2L)) {
if (summary.getDrivingCheck() == null) {
summary.setDrivingCheck(detail);
summary.setDrivingInspectLocation(detail.getInspectLocation());
}
return;
}
if (Objects.equals(inspectPhase, 3L) && summary.getBackCheck() == null) {
summary.setBackCheck(detail);
summary.setBackInspectLocation(detail.getInspectLocation());
}
}
private void fillTaskId(List<HotVehicleThreeInspectVo> list) {
if (list == null || list.isEmpty()) {
return;
}
String businessUserId = LoginHelper.getBusinessUserId();
for (HotVehicleThreeInspectVo vo : list) {
if (StringUtils.isNotBlank(vo.getInstanceId())) {
vo.setTaskId(flowService.getTaskId(vo.getInstanceId(), businessUserId));
}
}
}
private LambdaQueryWrapper<HotVehicleThreeInspect> buildQueryWrapper(HotVehicleThreeInspectBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotVehicleThreeInspect> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotVehicleThreeInspect::getCreateTime);
lqw.orderByDesc(HotVehicleThreeInspect::getCreateTime, HotVehicleThreeInspect::getId);
Object beginTime = params == null ? null : params.get("beginTime");
Object endTime = params == null ? null : params.get("endTime");
if (beginTime != null && endTime != null) {
@@ -220,6 +344,8 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
lqw.eq(bo.getInspectPhase() != null, HotVehicleThreeInspect::getInspectPhase, bo.getInspectPhase());
lqw.eq(bo.getInspectTime() != null, HotVehicleThreeInspect::getInspectTime, bo.getInspectTime());
lqw.eq(bo.getInspectId() != null, HotVehicleThreeInspect::getInspectId, bo.getInspectId());
lqw.eq(StringUtils.isNotBlank(bo.getInspectNo()), HotVehicleThreeInspect::getInspectNo, bo.getInspectNo());
lqw.like(StringUtils.isNotBlank(bo.getInspectLocation()), HotVehicleThreeInspect::getInspectLocation, bo.getInspectLocation());
lqw.eq(bo.getAuditorId() != null, HotVehicleThreeInspect::getAuditorId, bo.getAuditorId());
lqw.like(StringUtils.isNotBlank(bo.getAuditorName()), HotVehicleThreeInspect::getAuditorName, bo.getAuditorName());
lqw.eq(StringUtils.isNotBlank(bo.getInspectResultJson()), HotVehicleThreeInspect::getInspectResultJson, bo.getInspectResultJson());
@@ -237,6 +363,8 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
*/
@Override
public Boolean insertByBo(HotVehicleThreeInspectBo bo) {
normalizeInspectLocation(bo);
prepareInspectIdentity(bo);
validateAttachImgUrlRequiredWhenAbnormal(bo);
HotVehicleThreeInspect add = MapstructUtils.convert(bo, HotVehicleThreeInspect.class);
validEntityBeforeSave(add);
@@ -247,18 +375,12 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
// 校验该车辆三检流程逻辑
if (add.getVehicleId() != null) {
// 获取该车辆最近的一条三检记录
List<HotVehicleThreeInspect> list = baseMapper.selectList(
Wrappers.<HotVehicleThreeInspect>lambdaQuery()
.eq(HotVehicleThreeInspect::getVehicleId, add.getVehicleId())
.orderByDesc(HotVehicleThreeInspect::getCreateTime)
.last("LIMIT 1")
);
Long currentPhase = add.getInspectPhase();
// 获取该车辆最近的一条三检记录
HotVehicleThreeInspect last = selectLatestInspect(add.getVehicleId());
validateOutboundVehicleDriver(add, last);
if (list != null && !list.isEmpty()) {
HotVehicleThreeInspect last = list.get(0);
if (last != null) {
Long lastPhase = last.getInspectPhase();
// 1. 如果上一条是收车检查(第3阶段),则本次必须是新一轮的出车检查(第1阶段)
if (lastPhase == 3) {
@@ -307,6 +429,7 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
patchNewRoundInspectIdentity(add, bo);
bo.setId(add.getId());
// 开启车辆三检的流程
@@ -340,6 +463,45 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
return flag;
}
private void validateOutboundVehicleDriver(HotVehicleThreeInspect currentInspect, HotVehicleThreeInspect latestInspect) {
if (currentInspect == null || !Objects.equals(currentInspect.getInspectPhase(), 1L) || latestInspect == null) {
return;
}
if (Objects.equals(latestInspect.getInspectPhase(), 3L)) {
return;
}
if (StringUtils.equals(StringUtils.trimToEmpty(currentInspect.getDriverId()),
StringUtils.trimToEmpty(latestInspect.getDriverId()))) {
return;
}
throw new ServiceException(buildVehicleOccupiedMessage(currentInspect.getVehicleId()));
}
private String buildVehicleOccupiedMessage(Long vehicleId) {
if (vehicleId == null) {
return "该车辆已出任务,请重新选择其他车辆";
}
HotVehicle vehicle = hotVehicleMapper.selectById(String.valueOf(vehicleId));
String plateNumber = vehicle == null ? null : StringUtils.trimToNull(vehicle.getPlateNumber());
if (plateNumber == null) {
return "该车辆已出任务,请重新选择其他车辆";
}
return "'" + plateNumber + "'车辆已出任务,请重新选择其他车辆";
}
private boolean isVehicleOutbound(Long vehicleId) {
if (vehicleId == null) {
return false;
}
return hotVehicleInoutCheckMapper.selectCount(
Wrappers.<HotVehicleInoutCheck>lambdaQuery()
.eq(HotVehicleInoutCheck::getVehicleId, vehicleId)
.eq(HotVehicleInoutCheck::getIsDeleted, 0L)
.isNotNull(HotVehicleInoutCheck::getDepartureTime)
.isNull(HotVehicleInoutCheck::getReturnTime)
) > 0;
}
private void validateInspectorSignatureByPhase(HotVehicleThreeInspectBo bo) {
Long inspectPhase = bo.getInspectPhase();
if (inspectPhase == null) {
@@ -409,6 +571,7 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(HotVehicleThreeInspectBo bo) {
normalizeInspectLocation(bo);
validateAttachImgUrlRequiredWhenAbnormal(bo);
HotVehicleThreeInspect update = MapstructUtils.convert(bo, HotVehicleThreeInspect.class);
validEntityBeforeSave(update);
@@ -544,6 +707,15 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void multiVehicleBatchAudit(HotVehicleThreeInspectMultiVehicleBatchAuditBo bo) {
validateMultiVehicleBatchAuditRequest(bo);
HotVehicleThreeInspectBatchAuditBo batchAuditBo = new HotVehicleThreeInspectBatchAuditBo();
batchAuditBo.setAuditItems(buildMultiVehicleBatchAuditItems(bo));
batchAudit(batchAuditBo);
}
private void validateBatchAuditAuditor(HotVehicleThreeInspectBatchAuditBo bo) {
Long expectedAuditorId = null;
String expectedAuditorName = null;
@@ -661,8 +833,409 @@ public class HotVehicleThreeInspectServiceImpl implements IHotVehicleThreeInspec
Wrappers.<HotVehicleThreeInspect>lambdaQuery()
.eq(HotVehicleThreeInspect::getVehicleId, vehicleId)
.eq(HotVehicleThreeInspect::getIsDeleted, 0L)
.orderByDesc(HotVehicleThreeInspect::getCreateTime)
.orderByDesc(HotVehicleThreeInspect::getCreateTime, HotVehicleThreeInspect::getId)
.last("LIMIT 1")
);
}
private void enrichSummaryList(List<HotVehicleThreeInspectSummaryVo> summaryList) {
if (summaryList == null || summaryList.isEmpty()) {
return;
}
List<Long> inspectIds = summaryList.stream()
.map(HotVehicleThreeInspectSummaryVo::getInspectId)
.filter(Objects::nonNull)
.toList();
if (inspectIds.isEmpty()) {
return;
}
List<HotVehicleThreeInspectVo> detailList = baseMapper.querySummaryDetailList(inspectIds);
fillTaskId(detailList);
Map<Long, HotVehicleThreeInspectSummaryVo> summaryMap = new HashMap<>(summaryList.size());
for (HotVehicleThreeInspectSummaryVo summary : summaryList) {
summaryMap.put(summary.getInspectId(), summary);
}
for (HotVehicleThreeInspectVo detail : detailList) {
HotVehicleThreeInspectSummaryVo summary = summaryMap.get(detail.getInspectId());
if (summary == null) {
continue;
}
attachPhaseDetail(summary, detail);
}
for (HotVehicleThreeInspectSummaryVo summary : summaryList) {
refreshSummary(summary);
}
}
private void validateMultiVehicleBatchAuditRequest(HotVehicleThreeInspectMultiVehicleBatchAuditBo bo) {
if (bo == null || bo.getInspectIds() == null || bo.getInspectIds().isEmpty()) {
throw new ServiceException("三检流程ID不能为空");
}
if (Objects.equals(bo.getHasHiddenDanger(), 1L) && bo.getEvaluatorId() == null) {
throw new ServiceException("存在隐患时评估人不能为空");
}
}
private List<HotVehicleThreeInspectBo> buildMultiVehicleBatchAuditItems(HotVehicleThreeInspectMultiVehicleBatchAuditBo bo) {
Long currentAuditorId = resolveCurrentAuditorId();
Long currentCompanyId = resolveCurrentCompanyId();
LinkedHashSet<Long> inspectIdSet = new LinkedHashSet<>();
for (Long inspectId : bo.getInspectIds()) {
if (inspectId != null) {
inspectIdSet.add(inspectId);
}
}
if (inspectIdSet.isEmpty()) {
throw new ServiceException("三检流程ID不能为空");
}
log.info("车辆三检批量审核开始: inspectIds={}, currentAuditorId={}, currentCompanyId={}, hasHiddenDanger={}, evaluatorId={}",
inspectIdSet, currentAuditorId, currentCompanyId, bo.getHasHiddenDanger(), bo.getEvaluatorId());
List<HotVehicleThreeInspectVo> detailList = baseMapper.querySummaryDetailList(new ArrayList<>(inspectIdSet));
if (detailList == null || detailList.isEmpty()) {
throw new ServiceException("未查询到可批量审核的车辆三检数据");
}
fillTaskId(detailList);
log.info("车辆三检批量审核明细加载完成: detailCount={}, inspectCount={}", detailList.size(), inspectIdSet.size());
Map<Long, List<HotVehicleThreeInspectVo>> detailMap = new LinkedHashMap<>();
for (HotVehicleThreeInspectVo detail : detailList) {
if (detail == null || detail.getInspectId() == null) {
continue;
}
detailMap.computeIfAbsent(detail.getInspectId(), key -> new ArrayList<>()).add(detail);
}
Date auditTime = resolveBatchAuditTime(bo);
String auditResult = buildBatchAuditResult(bo);
List<HotVehicleThreeInspectBo> auditItems = new ArrayList<>();
for (Long inspectId : inspectIdSet) {
List<HotVehicleThreeInspectVo> currentDetails = detailMap.get(inspectId);
if (currentDetails == null || currentDetails.isEmpty()) {
throw new ServiceException("三检流程不存在inspectId=" + inspectId);
}
appendPendingAuditItems(bo, currentDetails, currentAuditorId, currentCompanyId, auditTime, auditResult, auditItems);
}
if (auditItems.isEmpty()) {
throw new ServiceException("没有符合批量审核条件的数据");
}
log.info("车辆三检批量审核审核项构建完成: inspectCount={}, auditItemCount={}, auditItemRecordIds={}",
inspectIdSet.size(),
auditItems.size(),
auditItems.stream().map(HotVehicleThreeInspectBo::getId).filter(Objects::nonNull).toList());
return auditItems;
}
private void appendPendingAuditItems(HotVehicleThreeInspectMultiVehicleBatchAuditBo bo,
List<HotVehicleThreeInspectVo> currentDetails,
Long currentAuditorId,
Long currentCompanyId,
Date auditTime,
String auditResult,
List<HotVehicleThreeInspectBo> auditItems) {
boolean hasBackCheck = false;
String plateNumber = null;
Long inspectId = null;
for (HotVehicleThreeInspectVo detail : currentDetails) {
if (detail == null) {
continue;
}
if (inspectId == null) {
inspectId = detail.getInspectId();
}
if (Objects.equals(detail.getInspectPhase(), 3L)) {
hasBackCheck = true;
}
if (StringUtils.isBlank(plateNumber)) {
plateNumber = resolvePlateNumber(detail.getVehicleId());
}
}
if (!hasBackCheck) {
throw new ServiceException(buildBatchAuditVehicleMessage(plateNumber, "尚未完成收车检查,不可批量审核"));
}
List<HotVehicleThreeInspectVo> candidateDetails = new ArrayList<>();
int skippedByCompany = 0;
int skippedByAuditor = 0;
int skippedByStatus = 0;
int skippedByTask = 0;
for (HotVehicleThreeInspectVo detail : currentDetails) {
if (detail == null) {
continue;
}
if (currentCompanyId != null && !Objects.equals(currentCompanyId, detail.getCompanyId())) {
skippedByCompany++;
continue;
}
if (!Objects.equals(currentAuditorId, detail.getAuditorId())) {
skippedByAuditor++;
continue;
}
if (!isPendingAuditRecord(detail)) {
skippedByStatus++;
continue;
}
if (StringUtils.isBlank(detail.getTaskId())) {
skippedByTask++;
continue;
}
candidateDetails.add(detail);
}
log.info("车辆三检批量审核候选明细筛选: inspectId={}, plateNumber={}, totalDetails={}, candidateCount={}, skippedByCompany={}, skippedByAuditor={}, skippedByStatus={}, skippedByTask={}, candidateRecordIds={}",
inspectId,
plateNumber,
currentDetails.size(),
candidateDetails.size(),
skippedByCompany,
skippedByAuditor,
skippedByStatus,
skippedByTask,
candidateDetails.stream().map(HotVehicleThreeInspectVo::getId).filter(Objects::nonNull).toList());
if (candidateDetails.isEmpty()) {
throw new ServiceException(buildBatchAuditVehicleMessage(plateNumber, "没有当前账号可审核的未审核数据"));
}
for (HotVehicleThreeInspectVo detail : candidateDetails) {
HotVehicleThreeInspectBo item = new HotVehicleThreeInspectBo();
item.setId(detail.getId());
item.setTaskId(detail.getTaskId());
item.setAuditTime(auditTime);
item.setHasHiddenDanger(bo.getHasHiddenDanger());
item.setAuditResult(auditResult);
item.setRejectReason(bo.getRejectReason());
item.setAuditorSignImgUrl(bo.getAuditorSignImgUrl());
item.setAuditorId(detail.getAuditorId());
item.setAuditorName(detail.getAuditorName());
item.setEvaluatorId(bo.getEvaluatorId());
auditItems.add(item);
}
log.info("车辆三检批量审核候选明细全部入队: inspectId={}, plateNumber={}, queuedCount={}, queuedRecordIds={}",
inspectId,
plateNumber,
candidateDetails.size(),
candidateDetails.stream().map(HotVehicleThreeInspectVo::getId).filter(Objects::nonNull).toList());
}
private boolean isPendingAuditRecord(HotVehicleThreeInspectVo detail) {
if (detail == null) {
return false;
}
return !"APPROVED".equals(detail.getFlowStatus()) && !"REJECTED".equals(detail.getFlowStatus());
}
private Date resolveBatchAuditTime(HotVehicleThreeInspectMultiVehicleBatchAuditBo bo) {
return bo.getAuditTime() != null ? bo.getAuditTime() : new Date();
}
private String buildBatchAuditResult(HotVehicleThreeInspectMultiVehicleBatchAuditBo bo) {
String auditResult = StringUtils.trimToNull(bo.getAuditResult());
if (auditResult != null) {
return auditResult;
}
if (Objects.equals(bo.getHasHiddenDanger(), 1L)) {
String rejectReason = StringUtils.trimToNull(bo.getRejectReason());
return rejectReason != null ? "不通过:" + rejectReason : "不通过";
}
return "通过";
}
private String buildBatchAuditVehicleMessage(String plateNumber, String suffix) {
String vehicleName = StringUtils.defaultIfBlank(StringUtils.trimToNull(plateNumber), "当前车辆");
return vehicleName + suffix;
}
private Long resolveCurrentAuditorId() {
String businessUserId = LoginHelper.getBusinessUserId();
if (StringUtils.isBlank(businessUserId)) {
throw new ServiceException("当前登录人员不存在");
}
try {
return Long.valueOf(businessUserId);
} catch (NumberFormatException e) {
throw new ServiceException("当前登录人员信息异常");
}
}
private Long resolveCurrentCompanyId() {
LoginUser loginUser = LoginHelper.getLoginUser();
return loginUser != null ? loginUser.getCompanyId() : null;
}
private void refreshSummary(HotVehicleThreeInspectSummaryVo summary) {
if (summary == null) {
return;
}
if (summary.getOutCheck() != null) {
summary.setStartTime(summary.getOutCheck().getInspectTime());
if (StringUtils.isBlank(summary.getInspectNo())) {
summary.setInspectNo(summary.getOutCheck().getInspectNo());
}
}
if (summary.getBackCheck() != null) {
summary.setEndTime(summary.getBackCheck().getInspectTime());
}
if (StringUtils.isBlank(summary.getInspectorName())) {
summary.setInspectorName(summary.getDriverName());
}
summary.setInspectTime(buildInspectTimeRange(summary.getStartTime(), summary.getEndTime()));
}
private String buildInspectTimeRange(Date startTime, Date endTime) {
if (startTime == null && endTime == null) {
return null;
}
if (startTime == null) {
return DateUtil.formatDateTime(endTime);
}
if (endTime == null || Objects.equals(startTime, endTime)) {
return DateUtil.formatDateTime(startTime);
}
return DateUtil.formatDateTime(startTime) + " ~ " + DateUtil.formatDateTime(endTime);
}
private void normalizeInspectLocation(HotVehicleThreeInspectBo bo) {
if (bo == null) {
return;
}
String inspectLocation = StringUtils.trimToNull(bo.getInspectLocation());
String reverseAddress = reverseGeocode(bo.getLatitude(), bo.getLongitude());
bo.setInspectLocation(StringUtils.defaultIfBlank(reverseAddress, inspectLocation));
}
private void prepareInspectIdentity(HotVehicleThreeInspectBo bo) {
if (bo == null || bo.getVehicleId() == null || bo.getInspectPhase() == null) {
return;
}
HotVehicleThreeInspect latestInspect = selectLatestInspect(bo.getVehicleId());
if (Objects.equals(bo.getInspectPhase(), 1L)) {
if (StringUtils.isBlank(bo.getInspectNo())) {
bo.setInspectNo(generateInspectNo(bo.getInspectTime(), bo.getVehicleId()));
}
return;
}
if (latestInspect == null) {
return;
}
if (bo.getInspectId() == null) {
bo.setInspectId(latestInspect.getInspectId() != null ? latestInspect.getInspectId() : latestInspect.getId());
}
if (StringUtils.isBlank(bo.getInspectNo())) {
bo.setInspectNo(latestInspect.getInspectNo());
}
}
private void patchNewRoundInspectIdentity(HotVehicleThreeInspect add, HotVehicleThreeInspectBo bo) {
if (add == null || !Objects.equals(add.getInspectPhase(), 1L)) {
return;
}
boolean changed = false;
if (add.getInspectId() == null) {
add.setInspectId(add.getId());
changed = true;
}
if (StringUtils.isBlank(add.getInspectNo())) {
add.setInspectNo(generateInspectNo(add.getInspectTime(), add.getVehicleId()));
changed = true;
}
if (!changed) {
return;
}
baseMapper.updateById(add);
bo.setInspectId(add.getInspectId());
bo.setInspectNo(add.getInspectNo());
}
private String generateInspectNo(Date inspectTime, Long vehicleId) {
Date recordDate = inspectTime == null ? new Date() : inspectTime;
String dateText = DateUtil.format(recordDate, "yyyyMMdd");
String plateSuffix = resolvePlateSuffix(vehicleId);
for (int i = 0; i < 10; i++) {
String candidate = "SJ" + dateText + plateSuffix + randomFourDigits();
Long count = baseMapper.selectCount(
Wrappers.<HotVehicleThreeInspect>lambdaQuery()
.eq(HotVehicleThreeInspect::getInspectNo, candidate)
);
if (count == null || count == 0L) {
return candidate;
}
}
return "SJ" + dateText + plateSuffix + String.format("%04d", System.currentTimeMillis() % 10000);
}
private String resolvePlateSuffix(Long vehicleId) {
if (vehicleId == null) {
return "000";
}
HotVehicle vehicle = hotVehicleMapper.selectById(String.valueOf(vehicleId));
String plateNumber = vehicle == null ? null : StringUtils.trimToNull(vehicle.getPlateNumber());
if (plateNumber == null) {
return "000";
}
String normalized = plateNumber.replace(" ", "");
if (normalized.length() <= 3) {
return normalized;
}
return normalized.substring(normalized.length() - 3);
}
private String resolvePlateNumber(Long vehicleId) {
if (vehicleId == null) {
return null;
}
HotVehicle vehicle = hotVehicleMapper.selectById(String.valueOf(vehicleId));
return vehicle == null ? null : StringUtils.trimToNull(vehicle.getPlateNumber());
}
private String randomFourDigits() {
return String.format("%04d", ThreadLocalRandom.current().nextInt(1000, 10000));
}
private String reverseGeocode(Double latitude, Double longitude) {
if (latitude == null || longitude == null) {
return null;
}
if (StringUtils.isBlank(gdKey)) {
return null;
}
String location = longitude + "," + latitude;
String url = "https://restapi.amap.com/v3/geocode/regeo?output=JSON&location="
+ location + "&key=" + gdKey + "&radius=1000&extensions=base";
HttpURLConnection connection = null;
try {
URL u = new URL(url);
connection = (HttpURLConnection) u.openConnection();
connection.setConnectTimeout(3000);
connection.setReadTimeout(3000);
connection.setRequestMethod("GET");
int code = connection.getResponseCode();
if (code != HttpURLConnection.HTTP_OK) {
return null;
}
try (InputStream is = connection.getInputStream()) {
String body = new String(is.readAllBytes(), StandardCharsets.UTF_8);
JsonNode root = JsonUtils.getObjectMapper().readTree(body);
if (root == null) {
return null;
}
if (!"1".equals(root.path("status").asText())) {
return null;
}
String formatted = root.path("regeocode").path("formatted_address").asText();
return StringUtils.trimToNull(formatted);
}
} catch (Exception e) {
log.warn("车辆三检逆地理解析失败: {},{}", latitude, longitude);
return null;
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}

View File

@@ -72,7 +72,7 @@ public class HotVehicleThreeInspectItemServiceImpl implements IHotVehicleThreeIn
private LambdaQueryWrapper<HotVehicleThreeInspectItem> buildQueryWrapper(HotVehicleThreeInspectItemBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotVehicleThreeInspectItem> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(HotVehicleThreeInspectItem::getSortNo);
lqw.orderByAsc(HotVehicleThreeInspectItem::getSortNo, HotVehicleThreeInspectItem::getId);
lqw.eq(bo.getCompanyId() != null, HotVehicleThreeInspectItem::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getInspectId() != null, HotVehicleThreeInspectItem::getInspectId, bo.getInspectId());
lqw.eq(bo.getInspectType() != null, HotVehicleThreeInspectItem::getInspectType, bo.getInspectType());

View File

@@ -270,7 +270,7 @@ public class HotDriverServiceImpl implements IHotDriverService {
private LambdaQueryWrapper<HotDriver> buildQueryWrapper(HotDriverBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotDriver> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotDriver::getCreateTime);
lqw.orderByDesc(HotDriver::getCreateTime, HotDriver::getId);
lqw.in(CollUtil.isNotEmpty(bo.getIds()), HotDriver::getId, bo.getIds());
lqw.eq(bo.getCompanyId() != null, HotDriver::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getEntryDate() != null, HotDriver::getEntryDate, bo.getEntryDate());
@@ -993,13 +993,11 @@ public class HotDriverServiceImpl implements IHotDriverService {
if (StringUtils.equals(driver.getVehicleId(), targetVehicleId)) {
return true;
}
if (StringUtils.isBlank(driver.getVehicleId())) {
throw new ServiceException("当前默认车辆不存在,不能切换");
if (StringUtils.isNotBlank(driver.getVehicleId())) {
hotVehicleThreeInspectService.validateNoUnfinishedSwitchVehicleProcess(driver.getVehicleId(),
"请先完成'" + StringUtils.defaultIfBlank(driver.getPlateNumber(), "当前车辆") + "'的车辆三检");
}
hotVehicleThreeInspectService.validateNoUnfinishedSwitchVehicleProcess(driver.getVehicleId(),
"请先完成'" + StringUtils.defaultIfBlank(driver.getPlateNumber(), "当前车辆") + "'的车辆三检");
HotVehicle targetVehicle = vehicleMapper.selectOne(Wrappers.<HotVehicle>lambdaQuery()
.eq(HotVehicle::getId, targetVehicleId)
.eq(HotVehicle::getCompanyId, driver.getCompanyId())

View File

@@ -76,7 +76,7 @@ public class HotDriverAnnualAssessmentServiceImpl implements IHotDriverAnnualAss
private LambdaQueryWrapper<HotDriverAnnualAssessment> buildQueryWrapper(HotDriverAnnualAssessmentBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotDriverAnnualAssessment> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotDriverAnnualAssessment::getAssessmentTime);
lqw.orderByDesc(HotDriverAnnualAssessment::getAssessmentTime, HotDriverAnnualAssessment::getId);
lqw.eq(bo.getCompanyId() != null, HotDriverAnnualAssessment::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getDriverId() != null, HotDriverAnnualAssessment::getDriverId, bo.getDriverId());
lqw.eq(StringUtils.isNotBlank(bo.getAssessmentContent()), HotDriverAnnualAssessment::getAssessmentContent, bo.getAssessmentContent());

View File

@@ -74,7 +74,7 @@ public class HotDriverHealthReportServiceImpl implements IHotDriverHealthReportS
private LambdaQueryWrapper<HotDriverHealthReport> buildQueryWrapper(HotDriverHealthReportBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotDriverHealthReport> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotDriverHealthReport::getReportDate);
lqw.orderByDesc(HotDriverHealthReport::getReportDate, HotDriverHealthReport::getId);
lqw.eq(bo.getCompanyId() != null, HotDriverHealthReport::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getDriverId() != null, HotDriverHealthReport::getDriverId, bo.getDriverId());
lqw.eq(bo.getReportDate() != null, HotDriverHealthReport::getReportDate, bo.getReportDate());

View File

@@ -137,6 +137,15 @@ public interface ISysFlowService {
*/
String getTaskIdByBusinessId(String flowCode, String businessId, String userId);
/**
* 根据业务ID获取当前活动任务ID不限定处理人
*
* @param flowCode 流程编码
* @param businessId 业务ID
* @return 当前活动任务ID
*/
String getCurrentTaskIdByBusinessId(String flowCode, String businessId);
/**
* 根据任务ID获取任务详情
*

View File

@@ -249,7 +249,7 @@ public class SysFlowServiceImpl implements ISysFlowService {
if (StrUtil.isNotBlank(isRead)) {
lqw.eq(SysFlowTask::getIsRead, Integer.valueOf(isRead));
}
lqw.orderByDesc(SysFlowTask::getCreateTime);
lqw.orderByDesc(SysFlowTask::getCreateTime, SysFlowTask::getTaskId);
List<SysFlowTask> records = taskMapper.selectList(lqw);
fillFlowInfo(records);
filterInvalidTrainingStudyTodos(records);
@@ -264,7 +264,7 @@ public class SysFlowServiceImpl implements ISysFlowService {
if (loginUser != null && cn.hutool.core.util.StrUtil.isNotBlank(loginUser.getBusinessUserId())) {
lqw.ne(SysFlowTask::getApproverId, loginUser.getBusinessUserId());
}
lqw.orderByDesc(SysFlowTask::getCreateTime);
lqw.orderByDesc(SysFlowTask::getCreateTime, SysFlowTask::getTaskId);
List<SysFlowTask> records = taskMapper.selectList(lqw);
fillFlowInfo(records);
filterInvalidTrainingStudyTodos(records);
@@ -634,7 +634,7 @@ public class SysFlowServiceImpl implements ISysFlowService {
public List<SysFlowTaskHis> getHistory(String instanceId) {
LambdaQueryWrapper<SysFlowTaskHis> lqw = new LambdaQueryWrapper<>();
lqw.eq(SysFlowTaskHis::getInstanceId, instanceId);
lqw.orderByAsc(SysFlowTaskHis::getAuditTime);
lqw.orderByAsc(SysFlowTaskHis::getAuditTime, SysFlowTaskHis::getHisId);
return taskHisMapper.selectList(lqw);
}
@@ -737,6 +737,32 @@ public class SysFlowServiceImpl implements ISysFlowService {
return task != null ? task.getTaskId() : null;
}
@Override
public String getCurrentTaskIdByBusinessId(String flowCode, String businessId) {
List<SysFlowInstance> instances = instanceMapper.selectList(
Wrappers.<SysFlowInstance>lambdaQuery()
.eq(SysFlowInstance::getFlowCode, flowCode)
.eq(SysFlowInstance::getBusinessId, businessId)
.eq(SysFlowInstance::getStatus, STATUS_RUNNING)
);
if (instances.isEmpty()) {
return null;
}
List<String> instanceIds = instances.stream()
.map(SysFlowInstance::getInstanceId)
.collect(Collectors.toList());
SysFlowTask task = taskMapper.selectOne(
Wrappers.<SysFlowTask>lambdaQuery()
.in(SysFlowTask::getInstanceId, instanceIds)
.orderByDesc(SysFlowTask::getCreateTime, SysFlowTask::getTaskId)
.last("limit 1")
);
return task != null ? task.getTaskId() : null;
}
@Override
public boolean hasPendingTask(String flowCode, String businessId, String userId) {
// 1. 查找相关的运行中流程实例

View File

@@ -91,7 +91,7 @@ public class HotCompanyNoticeServiceImpl implements IHotCompanyNoticeService {
lqw.eq(HotCompanyNotice::getIsPublished, 1L);
lqw.apply("NOT EXISTS (SELECT 1 FROM hot_company_notice_read r WHERE r.notice_id = hot_company_notice.id AND r.reader_id = {0} AND r.is_deleted = 0)", readerId);
lqw.orderByDesc(HotCompanyNotice::getIsTop);
lqw.orderByDesc(HotCompanyNotice::getCreateTime);
lqw.orderByDesc(HotCompanyNotice::getCreateTime, HotCompanyNotice::getId);
lqw.orderByDesc(HotCompanyNotice::getId);
lqw.last("limit 1");
HotCompanyNotice notice = baseMapper.selectOne(lqw);
@@ -104,7 +104,7 @@ public class HotCompanyNoticeServiceImpl implements IHotCompanyNoticeService {
private LambdaQueryWrapper<HotCompanyNotice> buildQueryWrapper(HotCompanyNoticeBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotCompanyNotice> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotCompanyNotice::getCreateTime);
lqw.orderByDesc(HotCompanyNotice::getCreateTime, HotCompanyNotice::getId);
Object beginTime = params.get("beginTime");
Object endTime = params.get("endTime");
if (beginTime != null && endTime != null) {
@@ -140,7 +140,7 @@ public class HotCompanyNoticeServiceImpl implements IHotCompanyNoticeService {
HotCompanyNotice maxSortNotice = baseMapper.selectOne(new LambdaQueryWrapper<HotCompanyNotice>()
.select(HotCompanyNotice::getSortNo)
.eq(HotCompanyNotice::getCompanyId, bo.getCompanyId())
.orderByDesc(HotCompanyNotice::getSortNo)
.orderByDesc(HotCompanyNotice::getSortNo, HotCompanyNotice::getId)
.last("limit 1"));
long maxSort = (maxSortNotice != null && maxSortNotice.getSortNo() != null) ? maxSortNotice.getSortNo() : 0L;
add.setSortNo(maxSort + 1);

View File

@@ -121,7 +121,7 @@ public class HotFileTypeConfigServiceImpl implements IHotFileTypeConfigService {
private LambdaQueryWrapper<HotFileTypeConfig> buildQueryWrapper(HotFileTypeConfigBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotFileTypeConfig> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(HotFileTypeConfig::getSortOrder).orderByDesc(BaseEntity::getCreateTime);
lqw.orderByAsc(HotFileTypeConfig::getSortOrder).orderByDesc(BaseEntity::getCreateTime).orderByDesc(HotFileTypeConfig::getId);
lqw.eq(bo.getCompanyId() != null, HotFileTypeConfig::getCompanyId, bo.getCompanyId());
lqw.like(StringUtils.isNotBlank(bo.getName()), HotFileTypeConfig::getName, bo.getName());
lqw.eq(StringUtils.isNotBlank(bo.getValue()), HotFileTypeConfig::getValue, bo.getValue());

View File

@@ -96,7 +96,7 @@ public class HotLawRegulationServiceImpl implements IHotLawRegulationService {
HotLawRegulation maxEntity = baseMapper.selectOne(Wrappers.<HotLawRegulation>lambdaQuery()
.select(HotLawRegulation::getSortno)
.eq(add.getCompanyId() != null, HotLawRegulation::getCompanyId, add.getCompanyId())
.orderByDesc(HotLawRegulation::getSortno)
.orderByDesc(HotLawRegulation::getSortno, HotLawRegulation::getId)
.last("LIMIT 1"));
Long maxSort = (maxEntity != null && maxEntity.getSortno() != null) ? maxEntity.getSortno() : 0L;
add.setSortno(maxSort + 1);

View File

@@ -359,7 +359,7 @@ public class HotNoticeReplyServiceImpl implements IHotNoticeReplyService {
.eq(HotCompanySafetyManager::getIsDeleted, 0L)
.eq(HotCompanySafetyManager::getIsResigned, 0L)
.eq(HotCompanySafetyManager::getStatus, 1L)
.orderByDesc(HotCompanySafetyManager::getCreateTime)
.orderByDesc(HotCompanySafetyManager::getCreateTime, HotCompanySafetyManager::getId)
);
if (heads != null && !heads.isEmpty()) {
return String.valueOf(heads.get(0).getId());
@@ -370,7 +370,7 @@ public class HotNoticeReplyServiceImpl implements IHotNoticeReplyService {
.eq(HotCompanySafetyManager::getIsDeleted, 0L)
.eq(HotCompanySafetyManager::getIsResigned, 0L)
.eq(HotCompanySafetyManager::getStatus, 1L)
.orderByDesc(HotCompanySafetyManager::getCreateTime)
.orderByDesc(HotCompanySafetyManager::getCreateTime, HotCompanySafetyManager::getId)
);
if (managers == null || managers.isEmpty()) {
return null;

View File

@@ -305,7 +305,7 @@ public class HotNotificationIssueController extends BaseController {
.eq(HotCompanySafetyManager::getIsDeleted, 0L)
.eq(HotCompanySafetyManager::getIsResigned, 0L)
.eq(HotCompanySafetyManager::getStatus, 1L)
.orderByDesc(HotCompanySafetyManager::getCreateTime)
.orderByDesc(HotCompanySafetyManager::getCreateTime, HotCompanySafetyManager::getId)
);
if (heads != null && !heads.isEmpty()) {
return String.valueOf(heads.get(0).getId());
@@ -316,7 +316,7 @@ public class HotNotificationIssueController extends BaseController {
.eq(HotCompanySafetyManager::getIsDeleted, 0L)
.eq(HotCompanySafetyManager::getIsResigned, 0L)
.eq(HotCompanySafetyManager::getStatus, 1L)
.orderByDesc(HotCompanySafetyManager::getCreateTime)
.orderByDesc(HotCompanySafetyManager::getCreateTime, HotCompanySafetyManager::getId)
);
if (managers == null || managers.isEmpty()) {
return null;

View File

@@ -127,7 +127,7 @@ public class HotSystemNotificationServiceImpl implements IHotSystemNotificationS
private LambdaQueryWrapper<HotSystemNotification> buildQueryWrapper(HotSystemNotificationBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotSystemNotification> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotSystemNotification::getSendTime);
lqw.orderByDesc(HotSystemNotification::getSendTime, HotSystemNotification::getId);
Object beginTime = params == null ? null : params.get("beginTime");
Object endTime = params == null ? null : params.get("endTime");
if (beginTime != null && endTime != null) {

View File

@@ -325,13 +325,13 @@ public class PayRechargeOpenServiceImpl implements PayRechargeOpenService {
.in(PayOrder::getStatus, PayOrderStatusEnum.WAITING.getStatus(), PayOrderStatusEnum.PAYING.getStatus())
.gt(PayOrder::getExpireTime, now)
.ge(PayOrder::getCreateTime, DateUtil.offsetMinute(now, -ORDER_EXPIRE_MINUTES))
.orderByAsc(PayOrder::getCreateTime)
.orderByAsc(PayOrder::getCreateTime, PayOrder::getId)
.last("limit " + COMPENSATE_BATCH_SIZE));
List<PayOrder> expiredOrders = payOrderMapper.selectList(Wrappers.<PayOrder>lambdaQuery()
.in(PayOrder::getStatus, PayOrderStatusEnum.WAITING.getStatus(), PayOrderStatusEnum.PAYING.getStatus())
.isNotNull(PayOrder::getExpireTime)
.le(PayOrder::getExpireTime, now)
.orderByAsc(PayOrder::getExpireTime)
.orderByAsc(PayOrder::getExpireTime, PayOrder::getId)
.last("limit " + COMPENSATE_BATCH_SIZE));
int queried = 0;

View File

@@ -109,7 +109,7 @@ public class HotPolicyRevisionServiceImpl implements IHotPolicyRevisionService {
private LambdaQueryWrapper<HotPolicyRevision> buildQueryWrapper(HotPolicyRevisionBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotPolicyRevision> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotPolicyRevision::getCreatedTime);
lqw.orderByDesc(HotPolicyRevision::getCreatedTime, HotPolicyRevision::getId);
lqw.eq(bo.getCompanyId() != null, HotPolicyRevision::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getPolicyId() != null, HotPolicyRevision::getPolicyId, bo.getPolicyId());
lqw.eq(bo.getVersionNo() != null, HotPolicyRevision::getVersionNo, bo.getVersionNo());
@@ -185,7 +185,7 @@ public class HotPolicyRevisionServiceImpl implements IHotPolicyRevisionService {
public Long nextVersionNo(Long policyId) {
LambdaQueryWrapper<HotPolicyRevision> lqw = Wrappers.lambdaQuery();
lqw.eq(HotPolicyRevision::getPolicyId, policyId);
lqw.orderByDesc(HotPolicyRevision::getVersionNo);
lqw.orderByDesc(HotPolicyRevision::getVersionNo, HotPolicyRevision::getId);
lqw.last("limit 1");
HotPolicyRevision last = baseMapper.selectOne(lqw);
Long lastNo = last != null ? last.getVersionNo() : 0L;

View File

@@ -1,7 +1,9 @@
package com.hotwj.platform.reportStatistics.controller;
import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateBo;
import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateSyncBo;
import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateVo;
import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateSyncCompareVo;
import com.hotwj.platform.reportStatistics.service.ISysReportTemplateService;
import com.hotwj.platform.reportStatistics.service.ReportRenderService;
import jakarta.servlet.http.HttpServletResponse;
@@ -97,6 +99,26 @@ public class SysReportTemplateController extends BaseController {
return R.ok();
}
/**
* 查询模板同步差异
*/
//@SaCheckPermission("report:template:sync")
@GetMapping("/sync/diff")
public R<List<SysReportTemplateSyncCompareVo>> syncDiff() {
return R.ok(sysReportTemplateService.querySyncDiffList());
}
/**
* 按选中项同步模板
*/
//@SaCheckPermission("report:template:sync")
@Log(title = "按项同步模版", businessType = BusinessType.UPDATE)
@PostMapping("/sync/selective")
public R<Void> selectiveSync(@Validated @RequestBody SysReportTemplateSyncBo bo) {
sysReportTemplateService.syncTemplatesFromClasspath(bo.getTemplateCodes());
return R.ok();
}
/**
* 删除报表模板
*/

View File

@@ -0,0 +1,19 @@
package com.hotwj.platform.reportStatistics.domain.bo;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* 报表模板按项同步请求
*/
@Data
public class SysReportTemplateSyncBo {
/**
* 需要同步的模板编码集合
*/
@NotEmpty(message = "请选择需要同步的模板")
private List<String> templateCodes;
}

View File

@@ -0,0 +1,45 @@
package com.hotwj.platform.reportStatistics.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 报表模板同步差异视图对象
*/
@Data
public class SysReportTemplateSyncCompareVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String templateCode;
private Long dbId;
private String localTemplateName;
private String dbTemplateName;
private String localContent;
private String dbContent;
private String localMockData;
private String dbMockData;
private String localRemark;
private String dbRemark;
private Boolean existsInLocal;
private Boolean existsInDb;
private Boolean syncable;
private List<String> diffTypes;
}

View File

@@ -2,6 +2,7 @@ package com.hotwj.platform.reportStatistics.service;
import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateBo;
import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateVo;
import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateSyncCompareVo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -56,6 +57,16 @@ public interface ISysReportTemplateService {
*/
void syncTemplatesFromClasspath();
/**
* 查询本地模板与数据库模板的差异
*/
List<SysReportTemplateSyncCompareVo> querySyncDiffList();
/**
* 按模板编码同步本地模板到数据库
*/
void syncTemplatesFromClasspath(Collection<String> templateCodes);
/**
* 根据模板编码查找对应的 Provider
*/

View File

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hotwj.platform.reportStatistics.domain.SysReportTemplate;
import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateBo;
import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateSyncCompareVo;
import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateVo;
import com.hotwj.platform.reportStatistics.mapper.SysReportTemplateMapper;
import com.hotwj.platform.reportStatistics.service.IReportDataProvider;
@@ -16,6 +17,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.core.io.ClassPathResource;
@@ -27,9 +29,15 @@ import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 报表模板Service业务层处理
@@ -42,6 +50,9 @@ import java.util.Map;
@Service
public class SysReportTemplateServiceImpl implements ISysReportTemplateService {
private static final String CLASSPATH_SCAN_REMARK = "Synced from classpath scan";
private static final String PROVIDER_REMARK_PREFIX = "Synced from Provider: ";
private final SysReportTemplateMapper baseMapper;
private final List<IReportDataProvider> reportDataProviders;
@@ -153,114 +164,257 @@ public class SysReportTemplateServiceImpl implements ISysReportTemplateService {
@Override
@Transactional(rollbackFor = Exception.class)
public void syncTemplatesFromClasspath() {
// 1. Sync from Providers (Source of truth for Title and MockData)
syncTemplatesFromClasspath(null);
}
@Override
public List<SysReportTemplateSyncCompareVo> querySyncDiffList() {
LinkedHashMap<String, LocalTemplateSnapshot> localTemplates = loadLocalTemplateSnapshots();
Map<String, SysReportTemplateVo> dbTemplates = queryExistingTemplates(null);
Set<String> allCodes = new LinkedHashSet<>();
allCodes.addAll(localTemplates.keySet());
allCodes.addAll(dbTemplates.keySet());
return allCodes.stream()
.map(code -> buildCompareVo(code, localTemplates.get(code), dbTemplates.get(code)))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncTemplatesFromClasspath(Collection<String> templateCodes) {
LinkedHashMap<String, LocalTemplateSnapshot> localTemplates = loadLocalTemplateSnapshots();
Set<String> targetCodes = normalizeTemplateCodes(templateCodes);
if (targetCodes != null && targetCodes.isEmpty()) {
throw new ServiceException("请选择需要同步的模板");
}
if (targetCodes != null) {
List<String> missingCodes = targetCodes.stream()
.filter(code -> !localTemplates.containsKey(code))
.collect(Collectors.toList());
if (!missingCodes.isEmpty()) {
throw new ServiceException("以下模板在本地不存在,无法同步: " + String.join(", ", missingCodes));
}
}
Map<String, SysReportTemplateVo> dbTemplates = queryExistingTemplates(targetCodes);
for (LocalTemplateSnapshot snapshot : localTemplates.values()) {
if (targetCodes != null && !targetCodes.contains(snapshot.getTemplateCode())) {
continue;
}
SysReportTemplateVo existing = dbTemplates.get(snapshot.getTemplateCode());
if (existing == null) {
SysReportTemplate insert = new SysReportTemplate();
insert.setTemplateCode(snapshot.getTemplateCode());
insert.setTemplateName(snapshot.getTemplateName());
insert.setContent(snapshot.getContent());
insert.setMockData(snapshot.getMockData());
insert.setVersion(1);
insert.setRemark(snapshot.getRemark());
baseMapper.insert(insert);
log.info("Inserted template from local source: {}", snapshot.getTemplateCode());
continue;
}
if (!hasDiff(snapshot, existing)) {
continue;
}
SysReportTemplate update = new SysReportTemplate();
update.setId(existing.getId());
update.setTemplateCode(snapshot.getTemplateCode());
update.setTemplateName(snapshot.getTemplateName());
update.setContent(snapshot.getContent());
update.setMockData(snapshot.getMockData());
update.setRemark(snapshot.getRemark());
update.setVersion(existing.getVersion() == null ? 1 : existing.getVersion() + 1);
baseMapper.updateById(update);
log.info("Updated template from local source: {}", snapshot.getTemplateCode());
}
}
private SysReportTemplateSyncCompareVo buildCompareVo(String code, LocalTemplateSnapshot local, SysReportTemplateVo db) {
List<String> diffTypes = buildDiffTypes(local, db);
if (diffTypes.isEmpty()) {
return null;
}
SysReportTemplateSyncCompareVo vo = new SysReportTemplateSyncCompareVo();
vo.setTemplateCode(code);
vo.setDbId(db == null ? null : db.getId());
vo.setLocalTemplateName(local == null ? "" : local.getTemplateName());
vo.setDbTemplateName(db == null ? "" : db.getTemplateName());
vo.setLocalContent(local == null ? "" : local.getContent());
vo.setDbContent(db == null ? "" : normalizeText(db.getContent()));
vo.setLocalMockData(toJsonText(local == null ? null : local.getMockData()));
vo.setDbMockData(toJsonText(db == null ? null : db.getMockData()));
vo.setLocalRemark(local == null ? "" : local.getRemark());
vo.setDbRemark(db == null ? "" : normalizeText(db.getRemark()));
vo.setExistsInLocal(local != null);
vo.setExistsInDb(db != null);
vo.setSyncable(local != null);
vo.setDiffTypes(diffTypes);
return vo;
}
private boolean hasDiff(LocalTemplateSnapshot local, SysReportTemplateVo db) {
return !buildDiffTypes(local, db).isEmpty();
}
private List<String> buildDiffTypes(LocalTemplateSnapshot local, SysReportTemplateVo db) {
List<String> diffTypes = new ArrayList<>();
if (local == null && db != null) {
diffTypes.add("仅数据库");
return diffTypes;
}
if (local != null && db == null) {
diffTypes.add("仅本地");
return diffTypes;
}
if (local == null) {
return diffTypes;
}
if (!Objects.equals(normalizeText(local.getTemplateName()), normalizeText(db.getTemplateName()))) {
diffTypes.add("名称");
}
if (!Objects.equals(normalizeText(local.getContent()), normalizeText(db.getContent()))) {
diffTypes.add("内容");
}
if (!Objects.equals(toJsonText(local.getMockData()), toJsonText(db.getMockData()))) {
diffTypes.add("模拟数据");
}
if (!Objects.equals(normalizeText(local.getRemark()), normalizeText(db.getRemark()))) {
diffTypes.add("备注");
}
return diffTypes;
}
private Map<String, SysReportTemplateVo> queryExistingTemplates(Set<String> templateCodes) {
LambdaQueryWrapper<SysReportTemplate> queryWrapper = Wrappers.lambdaQuery();
if (templateCodes != null && !templateCodes.isEmpty()) {
queryWrapper.in(SysReportTemplate::getTemplateCode, templateCodes);
}
return baseMapper.selectVoList(queryWrapper).stream()
.collect(Collectors.toMap(SysReportTemplateVo::getTemplateCode, item -> item, (left, right) -> left, LinkedHashMap::new));
}
private LinkedHashMap<String, LocalTemplateSnapshot> loadLocalTemplateSnapshots() {
LinkedHashMap<String, LocalTemplateSnapshot> templateMap = new LinkedHashMap<>();
if (reportDataProviders != null) {
for (IReportDataProvider provider : reportDataProviders) {
try {
String code = provider.getTemplateCode();
if (StringUtils.isBlank(code)) continue;
SysReportTemplateVo existing = queryByCode(code);
// Read content
String content = null;
try {
ClassPathResource resource = new ClassPathResource(code);
if (resource.exists()) {
content = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
}
} catch (Exception e) {
log.warn("Template file not found for provider: {}", provider.getClass().getSimpleName());
if (StringUtils.isBlank(code)) {
continue;
}
if (existing == null) {
if (StringUtils.isBlank(content)) {
log.warn("Skipping provider {} because template file {} not found", provider.getClass().getSimpleName(), code);
continue;
}
SysReportTemplate template = new SysReportTemplate();
template.setTemplateCode(code);
template.setTemplateName(provider.getReportTitle());
template.setContent(content);
template.setMockData(provider.getMockData());
template.setVersion(1);
template.setRemark("Synced from Provider: " + provider.getClass().getSimpleName());
baseMapper.insert(template);
log.info("Inserted template from provider: {}", code);
} else {
// Update metadata
SysReportTemplate update = new SysReportTemplate();
update.setId(existing.getId());
boolean changed = false;
// Sync Title
String title = provider.getReportTitle();
if (StringUtils.isNotBlank(title) && !title.equals(existing.getTemplateName())) {
update.setTemplateName(title);
changed = true;
}
// Sync MockData if provided
Map<String, Object> mockData = provider.getMockData();
if (mockData != null && !mockData.isEmpty()) {
update.setMockData(mockData);
changed = true;
}
// Optional: Sync content if it was empty in DB (unlikely)
if (StringUtils.isBlank(existing.getContent()) && StringUtils.isNotBlank(content)) {
update.setContent(content);
changed = true;
}
if (changed) {
baseMapper.updateById(update);
log.info("Updated template metadata from provider: {}", code);
}
String content = readTemplateContent(code);
if (StringUtils.isBlank(content)) {
log.warn("Skipping provider {} because template file {} not found", provider.getClass().getSimpleName(), code);
continue;
}
LocalTemplateSnapshot snapshot = new LocalTemplateSnapshot();
snapshot.setTemplateCode(code);
snapshot.setTemplateName(provider.getReportTitle());
snapshot.setContent(content);
snapshot.setMockData(provider.getMockData());
snapshot.setRemark(PROVIDER_REMARK_PREFIX + provider.getClass().getSimpleName());
templateMap.put(code, snapshot);
} catch (Exception e) {
log.error("Error syncing provider template: " + provider.getClass().getSimpleName(), e);
log.error("Error loading provider template: {}", provider.getClass().getSimpleName(), e);
}
}
}
// 2. Scan remaining files (for layouts and fragments)
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
// Find all .vm files in templates directory
Resource[] resources = resolver.getResources("classpath*:templates/**/*.vm");
for (Resource resource : resources) {
try {
String path = resource.getURL().getPath();
// Normalize path separators
path = path.replace("\\", "/");
int index = path.lastIndexOf("templates/");
if (index > -1) {
String code = path.substring(index); // Include "templates/" in the code
// Check if exists
SysReportTemplateVo exist = queryByCode(code);
if (exist == null) {
String content = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
SysReportTemplate template = new SysReportTemplate();
template.setTemplateCode(code);
// Use filename as name
template.setTemplateName(FileUtil.mainName(resource.getFilename()));
template.setContent(content);
template.setVersion(1);
template.setRemark("Synced from classpath scan");
baseMapper.insert(template);
log.info("Synced template from scan: {}", code);
}
String code = extractTemplateCode(resource);
if (StringUtils.isBlank(code) || templateMap.containsKey(code)) {
continue;
}
LocalTemplateSnapshot snapshot = new LocalTemplateSnapshot();
snapshot.setTemplateCode(code);
snapshot.setTemplateName(FileUtil.mainName(resource.getFilename()));
snapshot.setContent(IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8));
snapshot.setRemark(CLASSPATH_SCAN_REMARK);
templateMap.put(code, snapshot);
} catch (Exception e) {
log.error("Error processing resource: " + resource, e);
log.error("Error processing resource: {}", resource, e);
}
}
} catch (IOException e) {
log.error("Failed to sync templates", e);
throw new ServiceException("同步模版失败");
log.error("Failed to load local templates", e);
throw new ServiceException("读取本地模板失败");
}
return templateMap;
}
private String extractTemplateCode(Resource resource) throws IOException {
String path = resource.getURL().getPath().replace("\\", "/");
int index = path.lastIndexOf("templates/");
if (index < 0) {
return null;
}
return path.substring(index);
}
private String readTemplateContent(String code) {
try {
ClassPathResource resource = new ClassPathResource(code);
if (!resource.exists()) {
return null;
}
return IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
} catch (Exception e) {
log.warn("Template file not found: {}", code, e);
return null;
}
}
private Set<String> normalizeTemplateCodes(Collection<String> templateCodes) {
if (templateCodes == null) {
return null;
}
return templateCodes.stream()
.filter(StringUtils::isNotBlank)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
private String normalizeText(String value) {
return value == null ? "" : value;
}
private String toJsonText(Object value) {
if (value == null) {
return "";
}
return JsonUtils.toJsonString(value);
}
@lombok.Data
private static class LocalTemplateSnapshot {
private String templateCode;
private String templateName;
private String content;
private Object mockData;
private String remark;
}
}

View File

@@ -75,7 +75,7 @@ public class SysCompanyRoleServiceImpl implements ISysCompanyRoleService {
private LambdaQueryWrapper<SysCompanyRole> buildQueryWrapper(SysCompanyRoleBo bo) {
LambdaQueryWrapper<SysCompanyRole> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(SysCompanyRole::getCompanyId).orderByAsc(SysCompanyRole::getRoleSort);
lqw.orderByAsc(SysCompanyRole::getCompanyId).orderByAsc(SysCompanyRole::getRoleSort).orderByAsc(SysCompanyRole::getId);
lqw.eq(bo.getCompanyId() != null, SysCompanyRole::getCompanyId, bo.getCompanyId());
lqw.like(StringUtils.isNotBlank(bo.getRoleName()), SysCompanyRole::getRoleName, bo.getRoleName());
lqw.eq(StringUtils.isNotBlank(bo.getRoleKey()), SysCompanyRole::getRoleKey, bo.getRoleKey());
@@ -171,15 +171,29 @@ public class SysCompanyRoleServiceImpl implements ISysCompanyRoleService {
@Override
public Set<String> selectRoleKeys(Long userId, Long companyId) {
// Query HotCompanySafetyManager to get role ID
HotCompanySafetyManager manager = safetyManagerMapper.selectOne(Wrappers.<HotCompanySafetyManager>lambdaQuery()
.select(HotCompanySafetyManager::getPermissionRoleId)
// hot_company_safety_manager 可能存在重复有效数据,按最近一条记录兜底,避免登录鉴权直接失败
List<HotCompanySafetyManager> managers = safetyManagerMapper.selectList(Wrappers.<HotCompanySafetyManager>lambdaQuery()
.select(HotCompanySafetyManager::getId, HotCompanySafetyManager::getPermissionRoleId,
HotCompanySafetyManager::getUpdateTime, HotCompanySafetyManager::getCreateTime)
.eq(HotCompanySafetyManager::getUserId, userId)
.eq(HotCompanySafetyManager::getCompanyId, companyId)
.eq(HotCompanySafetyManager::getIsDeleted, 0)
.eq(HotCompanySafetyManager::getStatus, 1)
.and(w -> w.eq(HotCompanySafetyManager::getIsResigned, 0).or().isNull(HotCompanySafetyManager::getIsResigned))
);
.orderByDesc(HotCompanySafetyManager::getUpdateTime)
.orderByDesc(HotCompanySafetyManager::getCreateTime)
.orderByDesc(HotCompanySafetyManager::getId));
if (managers == null || managers.isEmpty()) {
return Collections.emptySet();
}
if (managers.size() > 1) {
log.warn("查询企业角色标识命中多条安全管理人员记录,按最新一条兜底返回 userId={}, companyId={}, count={}",
userId, companyId, managers.size());
}
HotCompanySafetyManager manager = managers.get(0);
if (manager == null || manager.getPermissionRoleId() == null) {
return Collections.emptySet();

View File

@@ -163,7 +163,7 @@ public class SysCompanyServiceImpl implements ISysCompanyService {
private LambdaQueryWrapper<SysCompany> buildQueryWrapper(SysCompanyBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SysCompany> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(SysCompany::getCreateTime);
lqw.orderByDesc(SysCompany::getCreateTime, SysCompany::getId);
lqw.like(StringUtils.isNotBlank(bo.getCompanyName()), SysCompany::getCompanyName, bo.getCompanyName());
lqw.like(StringUtils.isNotBlank(bo.getCompanyShortName()), SysCompany::getCompanyShortName, bo.getCompanyShortName());
lqw.eq(StringUtils.isNotBlank(bo.getCompanyType()), SysCompany::getCompanyType, bo.getCompanyType());
@@ -378,42 +378,63 @@ public class SysCompanyServiceImpl implements ISysCompanyService {
// 确保有企业管理员角色
Long roleId = createEnterpriseAdminRole(companyId, userId, responsibleName);
// 查找是否已存在该人员记录
LambdaQueryWrapper<HotCompanySafetyManager> smWrapper = new LambdaQueryWrapper<>();
smWrapper.eq(HotCompanySafetyManager::getCompanyId, companyId)
.eq(HotCompanySafetyManager::getPhone, responsibleMobile);
HotCompanySafetyManager safetyManager = hotCompanySafetyManagerMapper.selectOne(smWrapper);
HotCompanySafetyManager safetyManager = resolveResponsibleSafetyManager(companyId, userId, responsibleMobile);
if (safetyManager == null) {
safetyManager = new HotCompanySafetyManager();
safetyManager.setCompanyId(companyId);
safetyManager.setName(responsibleName);
safetyManager.setPhone(responsibleMobile);
safetyManager.setUserId(userId);
// 设置为企业负责人
safetyManager.setCompanyHead(1L);
// 企业负责人权限变更
safetyManager.setPermissionType("company_admin");
safetyManager.setPermissionRoleId(roleId);
safetyManager.setIsResigned(0L);
safetyManager.setStatus(1L);
fillResponsibleSafetyManager(safetyManager, companyId, responsibleName, responsibleMobile, userId, roleId);
hotCompanySafetyManagerMapper.insert(safetyManager);
log.info("[企业负责人] 创建安全管理人员记录 companyId={}, userId={}, managerId={}", companyId, userId, safetyManager.getId());
} else {
// 更新现有记录
safetyManager.setName(responsibleName);
safetyManager.setUserId(userId);
safetyManager.setCompanyHead(1L);
safetyManager.setPermissionType("company_admin");
safetyManager.setPermissionRoleId(roleId);
safetyManager.setIsResigned(0L);
safetyManager.setStatus(1L);
fillResponsibleSafetyManager(safetyManager, companyId, responsibleName, responsibleMobile, userId, roleId);
hotCompanySafetyManagerMapper.updateById(safetyManager);
log.info("[企业负责人] 更新安全管理人员记录 companyId={}, userId={}, managerId={}", companyId, userId, safetyManager.getId());
}
}
private HotCompanySafetyManager resolveResponsibleSafetyManager(Long companyId, Long userId, String responsibleMobile) {
List<HotCompanySafetyManager> matches = hotCompanySafetyManagerMapper.selectList(Wrappers.<HotCompanySafetyManager>lambdaQuery()
.eq(HotCompanySafetyManager::getCompanyId, companyId)
.eq(HotCompanySafetyManager::getUserId, userId)
.eq(HotCompanySafetyManager::getIsDeleted, 0L)
.orderByDesc(HotCompanySafetyManager::getCompanyHead)
.orderByDesc(HotCompanySafetyManager::getUpdateTime)
.orderByDesc(HotCompanySafetyManager::getCreateTime)
.orderByDesc(HotCompanySafetyManager::getId));
if (CollUtil.isEmpty(matches) && StringUtils.isNotBlank(responsibleMobile)) {
matches = hotCompanySafetyManagerMapper.selectList(Wrappers.<HotCompanySafetyManager>lambdaQuery()
.eq(HotCompanySafetyManager::getCompanyId, companyId)
.eq(HotCompanySafetyManager::getPhone, responsibleMobile)
.eq(HotCompanySafetyManager::getIsDeleted, 0L)
.orderByDesc(HotCompanySafetyManager::getCompanyHead)
.orderByDesc(HotCompanySafetyManager::getUpdateTime)
.orderByDesc(HotCompanySafetyManager::getCreateTime)
.orderByDesc(HotCompanySafetyManager::getId));
}
if (CollUtil.isEmpty(matches)) {
return null;
}
if (matches.size() > 1) {
log.warn("[企业负责人] 命中多条安全管理人员记录,按最新一条更新 companyId={}, userId={}, phone={}, count={}",
companyId, userId, responsibleMobile, matches.size());
}
return matches.get(0);
}
private void fillResponsibleSafetyManager(HotCompanySafetyManager safetyManager, Long companyId, String responsibleName,
String responsibleMobile, Long userId, Long roleId) {
safetyManager.setCompanyId(companyId);
safetyManager.setName(responsibleName);
safetyManager.setPhone(responsibleMobile);
safetyManager.setUserId(userId);
safetyManager.setCompanyHead(1L);
safetyManager.setPermissionType("company_admin");
safetyManager.setPermissionRoleId(roleId);
safetyManager.setIsDeleted(0L);
safetyManager.setIsResigned(0L);
safetyManager.setStatus(1L);
}
public Long createEnterpriseAdminRole(Long companyId, Long userId, String username) {
// 1. Check/Create SysCompanyRole
LambdaQueryWrapper<SysCompanyRole> roleWrapper = new LambdaQueryWrapper<>();
@@ -486,23 +507,7 @@ public class SysCompanyServiceImpl implements ISysCompanyService {
// 1. 处理旧负责人:移除相关权限,标记离职(仅当负责人手机号变更时)
String oldMobile = oldCompany.getResponsibleMobile();
if (StrUtil.isNotBlank(oldMobile) && !StrUtil.equals(oldMobile, bo.getResponsibleMobile())) {
SysUser oldUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, oldMobile));
if (oldUser != null) {
Long oldUserId = oldUser.getUserId();
// 移除该用户在当前企业的角色关联
sysUserRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getUserId, oldUserId)
.eq(SysUserRole::getLoginPort, ENTERPRISE_PORT)
.eq(SysUserRole::getCompanyId, bo.getId()));
// 移除该用户与企业的关联关系
sysUserLoginPortMapper.delete(new LambdaQueryWrapper<SysUserLoginPort>()
.eq(SysUserLoginPort::getUserId, oldUserId)
.eq(SysUserLoginPort::getLoginPort, ENTERPRISE_PORT)
.eq(SysUserLoginPort::getCompanyId, bo.getId()));
hotCompanySafetyManagerMapper.deleteCompanyHeadByPhone(bo.getId(), oldMobile);
}
cleanupOldResponsibleData(bo.getId(), oldMobile);
}
// 2. 添加新负责人:创建用户(如果不存在)、赋予权限、添加安全管理人员记录
@@ -512,6 +517,39 @@ public class SysCompanyServiceImpl implements ISysCompanyService {
return flag;
}
private void cleanupOldResponsibleData(Long companyId, String oldMobile) {
if (companyId == null || StrUtil.isBlank(oldMobile)) {
return;
}
SysUser oldUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, oldMobile));
Long oldUserId = oldUser != null ? oldUser.getUserId() : null;
if (oldUserId != null) {
sysUserRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getUserId, oldUserId)
.eq(SysUserRole::getLoginPort, ENTERPRISE_PORT)
.eq(SysUserRole::getCompanyId, companyId));
sysUserLoginPortMapper.delete(new LambdaQueryWrapper<SysUserLoginPort>()
.eq(SysUserLoginPort::getUserId, oldUserId)
.eq(SysUserLoginPort::getLoginPort, ENTERPRISE_PORT)
.eq(SysUserLoginPort::getCompanyId, companyId));
}
LambdaQueryWrapper<HotCompanySafetyManager> wrapper = Wrappers.<HotCompanySafetyManager>lambdaQuery()
.eq(HotCompanySafetyManager::getCompanyId, companyId)
.eq(HotCompanySafetyManager::getCompanyHead, 1L)
.eq(HotCompanySafetyManager::getIsDeleted, 0L)
.and(w -> {
w.eq(HotCompanySafetyManager::getPhone, oldMobile);
if (oldUserId != null) {
w.or().eq(HotCompanySafetyManager::getUserId, oldUserId);
}
});
int deleted = hotCompanySafetyManagerMapper.delete(wrapper);
if (deleted > 0) {
log.info("[企业负责人] 清理旧负责人安全管理人员记录 companyId={}, oldMobile={}, oldUserId={}, deleted={}",
companyId, oldMobile, oldUserId, deleted);
}
}
/**
* 保存前的数据校验
*/

View File

@@ -142,7 +142,7 @@ public class HotCompanyChangeHistoryServiceImpl implements IHotCompanyChangeHist
lqw.like(StringUtils.isNotBlank(bo.getName()), HotCompanyChangeHistory::getName, bo.getName());
lqw.like(StringUtils.isNotBlank(bo.getPhone()), HotCompanyChangeHistory::getPhone, bo.getPhone());
lqw.eq(StringUtils.isNotBlank(bo.getChangeType()), HotCompanyChangeHistory::getChangeType, bo.getChangeType());
lqw.orderByDesc(HotCompanyChangeHistory::getChangeTime);
lqw.orderByDesc(HotCompanyChangeHistory::getChangeTime, HotCompanyChangeHistory::getId);
return lqw;
}

View File

@@ -176,7 +176,7 @@ public class HotCompanyPermissionGrantServiceImpl implements IHotCompanyPermissi
lqw.eq(StringUtils.isNotBlank(bo.getPermissionCode()), HotCompanyPermissionGrant::getPermissionCode, bo.getPermissionCode());
lqw.eq(bo.getStatus() != null, HotCompanyPermissionGrant::getStatus, bo.getStatus());
lqw.eq(HotCompanyPermissionGrant::getIsDeleted, 0L);
lqw.orderByDesc(HotCompanyPermissionGrant::getCreateTime);
lqw.orderByDesc(HotCompanyPermissionGrant::getCreateTime, HotCompanyPermissionGrant::getId);
return lqw;
}

View File

@@ -107,7 +107,7 @@ public class HotCompanyPolicyServiceImpl implements IHotCompanyPolicyService {
.select(HotCompanyPolicy::getOrderNum)
.eq(HotCompanyPolicy::getCompanyId, bo.getCompanyId())
.eq(HotCompanyPolicy::getPolicyType, bo.getPolicyType())
.orderByDesc(HotCompanyPolicy::getOrderNum)
.orderByDesc(HotCompanyPolicy::getOrderNum, HotCompanyPolicy::getId)
.last("limit 1"));
long maxOrder = (maxOrderPolicy != null && maxOrderPolicy.getOrderNum() != null) ? maxOrderPolicy.getOrderNum() : 0L;
add.setOrderNum(maxOrder + 1);

View File

@@ -451,6 +451,8 @@ public class HotCompanySafetyManagerServiceImpl implements IHotCompanySafetyMana
userId = sysUser.getUserId();
}
validateManagerUniqueness(add.getCompanyId(), add.getPhone(), userId, null);
// 初始化或更新子密码(特定企业的登录密码)
String subPassword;
if (StringUtils.isNotBlank(add.getLoginPassword())) {
@@ -521,6 +523,9 @@ public class HotCompanySafetyManagerServiceImpl implements IHotCompanySafetyMana
if (before == null) {
throw new ServiceException("安全管理人员不存在");
}
Long finalCompanyId = update.getCompanyId() != null ? update.getCompanyId() : before.getCompanyId();
String finalPhone = StringUtils.isNotBlank(update.getPhone()) ? update.getPhone() : before.getPhone();
validateManagerUniqueness(finalCompanyId, finalPhone, before.getUserId(), update.getId());
// 参考新增:编辑时如果更换部门,且目标部门暂无负责人、当前人员又不是负责人,则不允许变更
boolean deptChanged = update.getDeptId() != null && !Objects.equals(update.getDeptId(), before.getDeptId());
Long finalIsDepartmentHead = update.getIsDepartmentHead() != null ? update.getIsDepartmentHead() : before.getIsDepartmentHead();
@@ -575,6 +580,39 @@ public class HotCompanySafetyManagerServiceImpl implements IHotCompanySafetyMana
);
}
private void validateManagerUniqueness(Long companyId, String phone, Long userId, Long excludeId) {
if (companyId == null) {
return;
}
if (StringUtils.isNotBlank(phone)) {
LambdaQueryWrapper<HotCompanySafetyManager> phoneWrapper = Wrappers.<HotCompanySafetyManager>lambdaQuery()
.eq(HotCompanySafetyManager::getCompanyId, companyId)
.eq(HotCompanySafetyManager::getPhone, phone)
.eq(HotCompanySafetyManager::getIsDeleted, 0L);
if (excludeId != null) {
phoneWrapper.ne(HotCompanySafetyManager::getId, excludeId);
}
if (baseMapper.selectCount(phoneWrapper) > 0) {
throw new ServiceException("同一公司下手机号不允许重复");
}
}
if (userId == null) {
return;
}
LambdaQueryWrapper<HotCompanySafetyManager> userWrapper = Wrappers.<HotCompanySafetyManager>lambdaQuery()
.eq(HotCompanySafetyManager::getCompanyId, companyId)
.eq(HotCompanySafetyManager::getUserId, userId)
.eq(HotCompanySafetyManager::getIsDeleted, 0L)
.eq(HotCompanySafetyManager::getStatus, 1L)
.and(w -> w.eq(HotCompanySafetyManager::getIsResigned, 0L).or().isNull(HotCompanySafetyManager::getIsResigned));
if (excludeId != null) {
userWrapper.ne(HotCompanySafetyManager::getId, excludeId);
}
if (baseMapper.selectCount(userWrapper) > 0) {
throw new ServiceException("同一公司下该用户已存在有效的安全管理人员记录");
}
}
private void syncUserAndRecordHistory(HotCompanySafetyManager update, HotCompanySafetyManager before, String changeReason, String beforeBusiness) {
SysUser sysUser = findUserByPhone(before.getPhone());
if (sysUser == null) {
@@ -689,7 +727,7 @@ public class HotCompanySafetyManagerServiceImpl implements IHotCompanySafetyMana
userService.resetUserPwd(sysUser.getUserId(), BCrypt.hashpw(newPwd), before.getCompanyId());
// 不需要单独更新 sysUser 密码resetUserPwd 已经处理
return false;
return false;
}
private boolean handlePhoneChange(HotCompanySafetyManager update, HotCompanySafetyManager before, SysUser sysUser) {

View File

@@ -88,7 +88,7 @@ public class HotVehicleAnnualReviewServiceImpl implements IHotVehicleAnnualRevie
return baseMapper.selectVoOne(new LambdaQueryWrapper<HotVehicleAnnualReview>()
.eq(HotVehicleAnnualReview::getVehicleId, vehicleId)
.eq(HotVehicleAnnualReview::getIsDeleted, 0L)
.orderByDesc(HotVehicleAnnualReview::getReviewDate)
.orderByDesc(HotVehicleAnnualReview::getReviewDate, HotVehicleAnnualReview::getId)
.last("LIMIT 1"));
}
@@ -97,7 +97,7 @@ public class HotVehicleAnnualReviewServiceImpl implements IHotVehicleAnnualRevie
LambdaQueryWrapper<HotVehicleAnnualReview> lqw = new LambdaQueryWrapper<HotVehicleAnnualReview>()
.eq(HotVehicleAnnualReview::getVehicleId, vehicleId)
.eq(HotVehicleAnnualReview::getIsDeleted, 0L)
.orderByDesc(HotVehicleAnnualReview::getReviewDate)
.orderByDesc(HotVehicleAnnualReview::getReviewDate, HotVehicleAnnualReview::getId)
.last("LIMIT 1");
if (startDate != null) {
lqw.ge(HotVehicleAnnualReview::getReviewDate, startDate);

View File

@@ -82,7 +82,7 @@ public class HotVehicleChangeServiceImpl implements IHotVehicleChangeService {
private LambdaQueryWrapper<HotVehicleChange> buildQueryWrapper(HotVehicleChangeBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotVehicleChange> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotVehicleChange::getCreateTime);
lqw.orderByDesc(HotVehicleChange::getCreateTime, HotVehicleChange::getId);
lqw.eq(bo.getCompanyId() != null, HotVehicleChange::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getVehicleId() != null, HotVehicleChange::getVehicleId, bo.getVehicleId());
lqw.like(StringUtils.isNotBlank(bo.getPlateNumber()), HotVehicleChange::getPlateNumber, bo.getPlateNumber());

View File

@@ -226,7 +226,7 @@ public class HotVehicleClaimServiceImpl implements IHotVehicleClaimService {
public HotVehicleClaimVo queryLatestByVehicleId(String vehicleId) {
return baseMapper.selectVoOne(new LambdaQueryWrapper<HotVehicleClaim>()
.eq(HotVehicleClaim::getVehicleId, vehicleId)
.orderByDesc(HotVehicleClaim::getIncidentTime)
.orderByDesc(HotVehicleClaim::getIncidentTime, HotVehicleClaim::getId)
.last("LIMIT 1"));
}
}

View File

@@ -81,7 +81,7 @@ public class HotVehicleMaintenanceServiceImpl implements IHotVehicleMaintenanceS
return baseMapper.selectVoOne(new LambdaQueryWrapper<HotVehicleMaintenance>()
.eq(HotVehicleMaintenance::getVehicleId, vehicleId)
.eq(HotVehicleMaintenance::getIsDeleted, 0L)
.orderByDesc(HotVehicleMaintenance::getFinishTime)
.orderByDesc(HotVehicleMaintenance::getFinishTime, HotVehicleMaintenance::getId)
.last("LIMIT 1"));
}

View File

@@ -85,6 +85,16 @@ public class HotVehicleController extends BaseController {
return hotVehicleService.queryPageListByStatus(bo, pageQuery);
}
/**
* 查询公司车辆信息列表(状态为数组,不过滤已绑定驾驶员)
*/
//@SaCheckPermission("resourceManagement:vehicle:list")
@GetMapping("/listByStatusAll")
@Operation(summary = "查询公司车辆信息列表(状态为数组,不过滤已绑定驾驶员)")
public TableDataInfo<HotVehicleVo> listByStatusAll(HotVehicleStatusBo bo, PageQuery pageQuery) {
return hotVehicleService.queryPageListByStatusAll(bo, pageQuery);
}
/**
* 查询当前企业可绑定的车辆列表
*/

View File

@@ -64,6 +64,15 @@ public interface IHotVehicleService {
*/
TableDataInfo<HotVehicleVo> queryPageListByStatus(HotVehicleStatusBo bo, PageQuery pageQuery);
/**
* 分页查询公司车辆信息列表(不过滤已绑定驾驶员)
*
* @param bo 查询条件 车辆状态为字符串逗号拼接
* @param pageQuery 分页参数
* @return 公司车辆信息分页列表
*/
TableDataInfo<HotVehicleVo> queryPageListByStatusAll(HotVehicleStatusBo bo, PageQuery pageQuery);
/**
* 查询当前企业可绑定的车辆列表
*

View File

@@ -195,7 +195,15 @@ public class HotVehicleServiceImpl implements IHotVehicleService {
*/
@Override
public TableDataInfo<HotVehicleVo> queryPageListByStatus(HotVehicleStatusBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<HotVehicle> lqw = buildQueryWrapperByStatus(bo);
LambdaQueryWrapper<HotVehicle> lqw = buildQueryWrapperByStatus(bo, true);
Page<HotVehicleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
fillCurrentBindingPersonName(result.getRecords());
return TableDataInfo.build(result);
}
@Override
public TableDataInfo<HotVehicleVo> queryPageListByStatusAll(HotVehicleStatusBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<HotVehicle> lqw = buildQueryWrapperByStatus(bo, false);
Page<HotVehicleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
fillCurrentBindingPersonName(result.getRecords());
return TableDataInfo.build(result);
@@ -212,6 +220,7 @@ public class HotVehicleServiceImpl implements IHotVehicleService {
LambdaQueryWrapper<HotVehicle> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(HotVehicle::getArchiveNo)
.orderByAsc(HotVehicle::getPlateNumber)
.orderByAsc(HotVehicle::getId)
.eq(bo.getCompanyId() != null, HotVehicle::getCompanyId, bo.getCompanyId())
.eq(HotVehicle::getVehicleStatus, 1L)
.eq(HotVehicle::getOperationStatus, 1L)
@@ -238,7 +247,8 @@ public class HotVehicleServiceImpl implements IHotVehicleService {
.eq(HotVehicle::getCompanyId, companyId)
.eq(HotVehicle::getIsDeleted, 0L)
.orderByAsc(HotVehicle::getArchiveNo)
.orderByAsc(HotVehicle::getPlateNumber));
.orderByAsc(HotVehicle::getPlateNumber)
.orderByAsc(HotVehicle::getId));
List<DriverBindingVehicleItemVo> boundVehicleList = new ArrayList<>();
List<DriverBindingVehicleItemVo> bindableVehicleList = new ArrayList<>();
@@ -266,7 +276,8 @@ public class HotVehicleServiceImpl implements IHotVehicleService {
.eq(HotVehicle::getCompanyId, companyId)
.eq(HotVehicle::getIsDeleted, 0L)
.orderByAsc(HotVehicle::getArchiveNo)
.orderByAsc(HotVehicle::getPlateNumber));
.orderByAsc(HotVehicle::getPlateNumber)
.orderByAsc(HotVehicle::getId));
List<DriverBindingVehicleItemVo> boundVehicleList = new ArrayList<>();
for (HotVehicle vehicle : companyVehicles) {
if (splitCsv(vehicle.getCurrentDriver()).contains(driverId)) {
@@ -330,7 +341,8 @@ public class HotVehicleServiceImpl implements IHotVehicleService {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotVehicle> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(HotVehicle::getOperationStatus)
.orderByAsc(HotVehicle::getArchiveNo);
.orderByAsc(HotVehicle::getArchiveNo)
.orderByAsc(HotVehicle::getId);
lqw.eq(bo.getCompanyId() != null, HotVehicle::getCompanyId, bo.getCompanyId());
lqw.in(CollUtil.isNotEmpty(bo.getIds()), HotVehicle::getId, bo.getIds());
lqw.eq(StringUtils.isNotBlank(bo.getGpsParams()), HotVehicle::getGpsParams, bo.getGpsParams());
@@ -413,18 +425,20 @@ public class HotVehicleServiceImpl implements IHotVehicleService {
return lqw;
}
private LambdaQueryWrapper<HotVehicle> buildQueryWrapperByStatus(HotVehicleStatusBo bo) {
private LambdaQueryWrapper<HotVehicle> buildQueryWrapperByStatus(HotVehicleStatusBo bo, boolean onlyUnboundDriver) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotVehicle> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotVehicle::getCreateTime);
lqw.orderByDesc(HotVehicle::getCreateTime, HotVehicle::getId);
lqw.eq(bo.getCompanyId() != null, HotVehicle::getCompanyId, bo.getCompanyId());
lqw.eq(StringUtils.isNotBlank(bo.getGpsParams()), HotVehicle::getGpsParams, bo.getGpsParams());
lqw.eq(bo.getIsTrailer() != null, HotVehicle::getIsTrailer, bo.getIsTrailer());
lqw.eq(bo.getVehicleStatus() != null, HotVehicle::getVehicleStatus, bo.getVehicleStatus());
lqw.in(HotVehicle::getOperationStatus, Arrays.asList(bo.getOperationStatus().split(",")));
lqw.and(wrapper -> wrapper.isNull(HotVehicle::getCurrentDriver)
.or()
.eq(HotVehicle::getCurrentDriver, ""));
if (onlyUnboundDriver) {
lqw.and(wrapper -> wrapper.isNull(HotVehicle::getCurrentDriver)
.or()
.eq(HotVehicle::getCurrentDriver, ""));
}
lqw.eq(StringUtils.isNotBlank(bo.getBodyImageUrls()), HotVehicle::getBodyImageUrls, bo.getBodyImageUrls());
lqw.eq(StringUtils.isNotBlank(bo.getOtherAttachmentUrls()), HotVehicle::getOtherAttachmentUrls, bo.getOtherAttachmentUrls());
lqw.eq(StringUtils.isNotBlank(bo.getColor()), HotVehicle::getColor, bo.getColor());

View File

@@ -129,7 +129,7 @@ public class HotVehicleMileageServiceImpl implements IHotVehicleMileageService {
public List<HotVehicleMileageVo> queryListByVehicleId(String vehicleId) {
LambdaQueryWrapper<HotVehicleMileage> lqw = Wrappers.lambdaQuery();
lqw.eq(HotVehicleMileage::getVehicleId, vehicleId);
lqw.orderByDesc(HotVehicleMileage::getStartDate);
lqw.orderByDesc(HotVehicleMileage::getStartDate, HotVehicleMileage::getId);
return baseMapper.selectVoList(lqw);
}

View File

@@ -87,7 +87,7 @@ public class HotVehicleRepairServiceImpl implements IHotVehicleRepairService {
public HotVehicleRepairVo queryLatestByVehicleId(String vehicleId) {
return baseMapper.selectVoOne(new LambdaQueryWrapper<HotVehicleRepair>()
.eq(HotVehicleRepair::getVehicleId, vehicleId)
.orderByDesc(HotVehicleRepair::getFinishDate)
.orderByDesc(HotVehicleRepair::getFinishDate, HotVehicleRepair::getId)
.last("LIMIT 1"));
}

View File

@@ -100,7 +100,7 @@ public class HotAccidentArchiveServiceImpl implements IHotAccidentArchiveService
private LambdaQueryWrapper<HotAccidentArchive> buildQueryWrapper(HotAccidentArchiveBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotAccidentArchive> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotAccidentArchive::getAccidentTime);
lqw.orderByDesc(HotAccidentArchive::getAccidentTime, HotAccidentArchive::getId);
if (bo.getAccidentTime() != null) {
java.util.Calendar calendarStart = java.util.Calendar.getInstance();
calendarStart.setTime(bo.getAccidentTime());

View File

@@ -122,7 +122,7 @@ public class HotHiddenDangerServiceImpl implements IHotHiddenDangerService {
} else if (hasEndTime) {
lqw.le(HotHiddenDanger::getCheckTime, endTime);
}
lqw.orderByDesc(HotHiddenDanger::getCreateTime);
lqw.orderByDesc(HotHiddenDanger::getCreateTime, HotHiddenDanger::getId);
lqw.eq(bo.getCompanyId() != null, HotHiddenDanger::getCompanyId, bo.getCompanyId());
lqw.eq(StringUtils.isNotBlank(bo.getDangerNo()), HotHiddenDanger::getDangerNo, bo.getDangerNo());
lqw.like(StringUtils.isNotBlank(bo.getDangerName()), HotHiddenDanger::getDangerName, bo.getDangerName());

View File

@@ -131,11 +131,12 @@ public class HotHiddenDangerInspectionController extends BaseController {
@GetMapping("/hidden-danger-plans/page")
@Operation(
summary = "移动端分页查询隐患排查计划列表",
description = "权限说明:驾驶员仅可查看本人及所属车辆当月全部计划和历史未做计划;管理员可查看企业当月全部计划和历史未做计划。"
description = "权限说明:驾驶员仅可查看当前处于原始周期或补查周期内、且属于本人及所属车辆的计划;管理员可查看企业内当前处于有效时间范围的计划。"
)
public TableDataInfo<HotHiddenDangerInspectionVo> hiddenDangerPlanPage(
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "planId", required = false) Long planId,
PageQuery pageQuery) {
return hotHiddenDangerInspectionService.queryMobilePlanPage(keyword, pageQuery);
return hotHiddenDangerInspectionService.queryMobilePlanPage(keyword, planId, pageQuery);
}
}

View File

@@ -80,6 +80,16 @@ public class HotHiddenDangerInspection extends BaseEntity {
*/
private Date checkDate;
/**
* 是否补查期间处理0否 1是
*/
private Long isRecheckProcessed;
/**
* 补查处理时间
*/
private Date recheckProcessedTime;
/**
* 评估人id
*/
@@ -150,4 +160,39 @@ public class HotHiddenDangerInspection extends BaseEntity {
* 流程状态
*/
private String flowStatus;
/**
* 提交版本号
*/
private Long submitVersion;
/**
* 审核结果PASS=通过 REJECT=驳回)
*/
private String auditResult;
/**
* 驳回次数
*/
private Long rejectCount;
/**
* 最近一次驳回原因
*/
private String lastRejectReason;
/**
* 最近一次驳回时间
*/
private Date lastRejectTime;
/**
* 最近一次驳回人ID
*/
private Long lastRejectById;
/**
* 最近一次驳回人姓名
*/
private String lastRejectByName;
}

View File

@@ -0,0 +1,150 @@
package com.hotwj.platform.securityManagement.hiddenDangerInspection.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
import java.util.Date;
/**
* 隐患排查审核驳回历史对象 hot_hidden_danger_inspection_audit_history
*
* @author shihongwei
* @date 2026-05-30
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hot_hidden_danger_inspection_audit_history")
public class HotHiddenDangerInspectionAuditHistory extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id")
private Long id;
/**
* 隐患排查主单ID
*/
private Long inspectionId;
/**
* 公司ID
*/
private Long companyId;
/**
* 计划ID
*/
private Long planId;
/**
* 排查项目
*/
private String projectName;
/**
* 排查项目ID
*/
private String projectId;
/**
* 排查类型1=公司 2=车辆 3=人员)
*/
private Long projectType;
/**
* 提交版本号
*/
private Long submitVersion;
/**
* 审核轮次
*/
private Long auditRound;
/**
* 审核结果REJECT
*/
private String auditResult;
/**
* 驳回原因
*/
private String rejectReason;
/**
* 审核日期
*/
private Date auditDate;
/**
* 审核人ID
*/
private Long approverId;
/**
* 审核人姓名
*/
private String approverName;
/**
* 审核人签名
*/
private String approverSignImgUrl;
/**
* 审核时是否存在隐患
*/
private Long auditHasDanger;
/**
* 当次提交快照JSON
*/
private String submitSnapshotJson;
/**
* 排查内容JSON
*/
private String checkContentJson;
/**
* 隐患描述
*/
private String dangerDesc;
/**
* 附件
*/
private String attachmentUrl;
/**
* 排查人签名
*/
private String signImgUrl;
/**
* 排查人ID
*/
private String checkerId;
/**
* 排查人姓名
*/
private String checkerName;
/**
* 排查日期
*/
private Date checkDate;
/**
* 0=正常,1=已删除
*/
@TableLogic
private Long isDeleted;
}

View File

@@ -86,6 +86,16 @@ public class HotHiddenDangerInspectionBo extends BaseEntity {
// @NotNull(message = "排查日期不能为空", groups = {AddGroup.class, EditGroup.class})
private Date checkDate;
/**
* 是否补查期间处理0否 1是
*/
private Long isRecheckProcessed;
/**
* 补查处理时间
*/
private Date recheckProcessedTime;
/**
* 评估人id
*/
@@ -159,6 +169,41 @@ public class HotHiddenDangerInspectionBo extends BaseEntity {
*/
private String flowStatus;
/**
* 提交版本号
*/
private Long submitVersion;
/**
* 审核结果PASS=通过 REJECT=驳回)
*/
private String auditResult;
/**
* 驳回次数
*/
private Long rejectCount;
/**
* 最近一次驳回原因
*/
private String lastRejectReason;
/**
* 最近一次驳回时间
*/
private Date lastRejectTime;
/**
* 最近一次驳回人ID
*/
private Long lastRejectById;
/**
* 最近一次驳回人姓名
*/
private String lastRejectByName;
/**
* 流程任务ID

View File

@@ -0,0 +1,71 @@
package com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.vo;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.HotHiddenDangerInspectionAuditHistory;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 隐患排查审核驳回历史视图对象
*
* @author shihongwei
* @date 2026-05-30
*/
@Data
@AutoMapper(target = HotHiddenDangerInspectionAuditHistory.class)
public class HotHiddenDangerInspectionAuditHistoryVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
private Long inspectionId;
private Long companyId;
private Long planId;
private String projectName;
private String projectId;
private Long projectType;
private Long submitVersion;
private Long auditRound;
private String auditResult;
private String rejectReason;
private Date auditDate;
private Long approverId;
private String approverName;
private String approverSignImgUrl;
private Long auditHasDanger;
private String submitSnapshotJson;
private String checkContentJson;
private String dangerDesc;
private String attachmentUrl;
private String signImgUrl;
private String checkerId;
private String checkerName;
private Date checkDate;
}

View File

@@ -11,6 +11,7 @@ import org.dromara.common.excel.convert.ExcelDictConvert;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
@@ -93,6 +94,18 @@ public class HotHiddenDangerInspectionVo implements Serializable {
@ExcelProperty(value = "排查日期")
private Date checkDate;
/**
* 是否补查期间处理0否 1是
*/
@ExcelProperty(value = "是否补查期间处理")
private Long isRecheckProcessed;
/**
* 补查处理时间
*/
@ExcelProperty(value = "补查处理时间")
private Date recheckProcessedTime;
/**
* 评估人id
*/
@@ -178,6 +191,41 @@ public class HotHiddenDangerInspectionVo implements Serializable {
@ExcelProperty(value = "流程状态")
private String flowStatus;
/**
* 提交版本号
*/
private Long submitVersion;
/**
* 审核结果PASS=通过 REJECT=驳回)
*/
private String auditResult;
/**
* 驳回次数
*/
private Long rejectCount;
/**
* 最近一次驳回原因
*/
private String lastRejectReason;
/**
* 最近一次驳回时间
*/
private Date lastRejectTime;
/**
* 最近一次驳回人ID
*/
private Long lastRejectById;
/**
* 最近一次驳回人姓名
*/
private String lastRejectByName;
/**
* 流程任务ID
*/
@@ -192,4 +240,9 @@ public class HotHiddenDangerInspectionVo implements Serializable {
private Integer todoSortGroup;
private String todoSortLabel;
/**
* 审核不通过历史
*/
private List<HotHiddenDangerInspectionAuditHistoryVo> rejectHistoryList;
}

View File

@@ -0,0 +1,17 @@
package com.hotwj.platform.securityManagement.hiddenDangerInspection.mapper;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.HotHiddenDangerInspectionAuditHistory;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.vo.HotHiddenDangerInspectionAuditHistoryVo;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 隐患排查审核驳回历史Mapper接口
*
* @author shihongwei
* @date 2026-05-30
*/
@Mapper
public interface HotHiddenDangerInspectionAuditHistoryMapper
extends BaseMapperPlus<HotHiddenDangerInspectionAuditHistory, HotHiddenDangerInspectionAuditHistoryVo> {
}

View File

@@ -23,6 +23,6 @@ public interface HotHiddenDangerInspectionMapper extends BaseMapperPlus<HotHidde
@Param("isAdmin") Boolean isAdmin,
@Param("driverId") String driverId,
@Param("vehicleId") String vehicleId,
@Param("keyword") String keyword,
@Param("currentMonth") String currentMonth);
@Param("planId") Long planId,
@Param("keyword") String keyword);
}

View File

@@ -33,7 +33,7 @@ public interface IHotHiddenDangerInspectionService {
*/
TableDataInfo<HotHiddenDangerInspectionVo> queryPageList(HotHiddenDangerInspectionBo bo, PageQuery pageQuery);
TableDataInfo<HotHiddenDangerInspectionVo> queryMobilePlanPage(String keyword, PageQuery pageQuery);
TableDataInfo<HotHiddenDangerInspectionVo> queryMobilePlanPage(String keyword, Long planId, PageQuery pageQuery);
/**
* 查询符合条件的隐患排查-排查项目列表

View File

@@ -1,5 +1,6 @@
package com.hotwj.platform.securityManagement.hiddenDangerInspection.service.impl;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -10,9 +11,15 @@ import com.hotwj.platform.integration.ocr.SignatureVerifyService;
import com.hotwj.platform.noticeManagerment.systemNotification.domain.bo.HotSystemNotificationGroupBo;
import com.hotwj.platform.noticeManagerment.systemNotification.service.IHotSystemNotificationService;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.HotHiddenDangerInspection;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.HotHiddenDangerInspectionAuditHistory;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.bo.HotHiddenDangerInspectionBo;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.vo.HotHiddenDangerInspectionAuditHistoryVo;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.domain.vo.HotHiddenDangerInspectionVo;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.mapper.HotHiddenDangerInspectionAuditHistoryMapper;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.mapper.HotHiddenDangerInspectionMapper;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.HotHiddenDangerPlan;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.mapper.HotHiddenDangerPlanMapper;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.service.HiddenDangerPlanTimeWindowHelper;
import com.hotwj.platform.securityManagement.hiddenDangerInspection.service.IHotHiddenDangerInspectionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -25,8 +32,9 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.YearMonth;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -42,9 +50,12 @@ import java.util.Map;
public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerInspectionService {
private final HotHiddenDangerInspectionMapper baseMapper;
private final HotHiddenDangerInspectionAuditHistoryMapper historyMapper;
private final ISysFlowService flowService;
private final IHotSystemNotificationService notificationService;
private final SignatureVerifyService signatureVerifyService;
private final HotHiddenDangerPlanMapper planMapper;
private final HiddenDangerPlanTimeWindowHelper timeWindowHelper;
/**
* 查询隐患排查-排查项目
@@ -55,10 +66,11 @@ public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerIns
@Override
public HotHiddenDangerInspectionVo queryById(Long id) {
HotHiddenDangerInspectionVo vo = baseMapper.selectVoById(id);
if (vo != null && StringUtils.isNotBlank(vo.getInstanceId())) {
String userId = LoginHelper.getBusinessUserId();
vo.setTaskId(flowService.getTaskId(vo.getInstanceId(), userId));
if (vo == null) {
return null;
}
vo.setRejectHistoryList(queryRejectHistoryList(id));
fillTaskId(vo, LoginHelper.getBusinessUserId());
return vo;
}
@@ -80,7 +92,7 @@ public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerIns
}
@Override
public TableDataInfo<HotHiddenDangerInspectionVo> queryMobilePlanPage(String keyword, PageQuery pageQuery) {
public TableDataInfo<HotHiddenDangerInspectionVo> queryMobilePlanPage(String keyword, Long planId, PageQuery pageQuery) {
Long companyId = LoginHelper.getCompanyId();
if (companyId == null) {
throw new ServiceException("当前登录用户无公司信息");
@@ -93,7 +105,6 @@ public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerIns
driverId = driver.getId();
vehicleId = driver.getVehicleId();
}
String currentMonth = YearMonth.now().toString();
Page<HotHiddenDangerInspectionVo> page = pageQuery.build();
List<HotHiddenDangerInspectionVo> rows = baseMapper.selectMobilePlanPage(
page,
@@ -101,8 +112,8 @@ public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerIns
!isDriver,
driverId,
vehicleId,
StringUtils.trimToNull(keyword),
currentMonth
planId,
StringUtils.trimToNull(keyword)
);
page.setRecords(rows);
fillFlowExtras(rows);
@@ -156,11 +167,11 @@ public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerIns
if (vo == null) {
continue;
}
if (StringUtils.isNotBlank(vo.getInstanceId())) {
vo.setTaskId(flowService.getTaskId(vo.getInstanceId(), userId));
}
fillTaskId(vo, userId);
if (StringUtils.isBlank(vo.getFlowStatus())) {
if (vo.getStatus() == null || vo.getStatus() == 0L) {
if ("REJECT".equalsIgnoreCase(vo.getAuditResult())) {
vo.setFlowStatus("REJECTED");
} else if (vo.getStatus() == null || vo.getStatus() == 0L || vo.getStatus() == 1L) {
vo.setFlowStatus("TODO");
} else if (vo.getStatus() == 3L || vo.getStatus() == 9L) {
vo.setFlowStatus("DONE");
@@ -195,36 +206,66 @@ public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerIns
* @return 是否修改成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(HotHiddenDangerInspectionBo bo) {
HotHiddenDangerInspection original = baseMapper.selectById(bo.getId());
if (original == null) {
throw new ServiceException("排查记录不存在");
}
HotHiddenDangerPlan plan = getPlanByInspectionOrThrow(original);
Date now = new Date();
if (!timeWindowHelper.canProcess(now, plan)) {
throw new ServiceException("当前计划未处于可处理时间范围内");
}
HotHiddenDangerInspection update = MapstructUtils.convert(bo, HotHiddenDangerInspection.class);
validEntityBeforeSave(update);
markRecheckProcessIfNeeded(update, original, plan, now);
String signImgUrl = bo.getSignImgUrl();
if (DriverLoginContextHelper.isDriverPort() && StringUtils.isNotBlank(signImgUrl)) {
String checkerName = bo.getCheckerName();
if (original == null || StringUtils.isBlank(checkerName)) {
if (StringUtils.isBlank(checkerName)) {
throw new ServiceException("排查人姓名不能为空");
}
signatureVerifyService.validateSelfSignature(parseSignatureOssId(signImgUrl), checkerName);
}
boolean flag = baseMapper.updateById(update) > 0;
if (flag && (original == null || StringUtils.isBlank(original.getInstanceId()))) {
if (flag && shouldResumeRejectedFlow(original)) {
resumeRejectedFlow(original);
} else if (flag && shouldStartAuditFlow(original)) {
String initiator = String.valueOf(LoginHelper.getBusinessUserId());
String approverId = bo.getApproverId() != null ? String.valueOf(bo.getApproverId()) : null;
if (StringUtils.isBlank(approverId)) {
throw new ServiceException("请选择审核人");
}
Long nextSubmitVersion = buildNextSubmitVersion(original);
String instanceId = flowService.startFlow(
"HIDDEN_DANGER_CHECK",
String.valueOf(update.getId()),
update.getCompanyId(),
String.valueOf(original.getId()),
original.getCompanyId(),
initiator,
approverId
);
update.setInstanceId(instanceId);
update.setFlowStatus("SUBMITTED");
baseMapper.updateById(update);
baseMapper.update(
null,
Wrappers.<HotHiddenDangerInspection>lambdaUpdate()
.eq(HotHiddenDangerInspection::getId, original.getId())
.set(HotHiddenDangerInspection::getStatus, 2L)
.set(HotHiddenDangerInspection::getInstanceId, instanceId)
.set(HotHiddenDangerInspection::getFlowStatus, "SUBMITTED")
.set(HotHiddenDangerInspection::getSubmitVersion, nextSubmitVersion)
.set(HotHiddenDangerInspection::getAuditDate, null)
.set(HotHiddenDangerInspection::getAuditConclusion, null)
.set(HotHiddenDangerInspection::getApproverSignImgUrl, null)
.set(HotHiddenDangerInspection::getAuditHasDanger, null)
.set(HotHiddenDangerInspection::getAuditResult, null)
.set(HotHiddenDangerInspection::getEvaluatorId, null)
.set(HotHiddenDangerInspection::getEvaluatorName, null)
.set(HotHiddenDangerInspection::getLastRejectReason, null)
.set(HotHiddenDangerInspection::getLastRejectTime, null)
.set(HotHiddenDangerInspection::getLastRejectById, null)
.set(HotHiddenDangerInspection::getLastRejectByName, null)
);
try {
HotSystemNotificationGroupBo bos = new HotSystemNotificationGroupBo();
@@ -242,6 +283,25 @@ public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerIns
}
return flag;
}
/**
* 标记是否需要补查处理
*/
private void markRecheckProcessIfNeeded(HotHiddenDangerInspection update,
HotHiddenDangerInspection original,
HotHiddenDangerPlan plan,
Date now) {
if (update == null || original == null || plan == null || now == null) {
return;
}
if (!timeWindowHelper.isInRecheckWindow(now, plan)) {
return;
}
if (Long.valueOf(1L).equals(original.getIsRecheckProcessed())) {
return;
}
update.setIsRecheckProcessed(1L);
update.setRecheckProcessedTime(now);
}
private Long parseSignatureOssId(String signImgUrl) {
String first = signImgUrl.split(",")[0].trim();
@@ -254,26 +314,273 @@ public class HotHiddenDangerInspectionServiceImpl implements IHotHiddenDangerIns
@Override
@Transactional(rollbackFor = Exception.class)
public void audit(HotHiddenDangerInspectionBo bo) {
// 1. 校验参数
if (bo.getTaskId() == null) {
if (StringUtils.isBlank(bo.getTaskId())) {
throw new ServiceException("流程任务ID不能为空");
}
// 2. 更新业务数据
HotHiddenDangerInspection update = MapstructUtils.convert(bo, HotHiddenDangerInspection.class);
boolean flag = baseMapper.updateById(update) > 0;
if (flag) {
// 4. 完成流程任务
// 如果有隐患,我们认为流程是"驳回"的(需要整改)
boolean auditPass = bo.getAuditHasDanger() != null && bo.getAuditHasDanger() == 0L;
flowService.audit(
bo.getTaskId(),
auditPass,
bo.getAuditConclusion(),
LoginHelper.getBusinessUserId()
);
if (bo.getId() == null) {
throw new ServiceException("排查记录ID不能为空");
}
HotHiddenDangerInspection original = baseMapper.selectById(bo.getId());
if (original == null) {
throw new ServiceException("排查记录不存在");
}
String auditResult = normalizeAuditResult(bo);
String auditConclusion = StringUtils.trimToNull(bo.getAuditConclusion());
Date auditDate = bo.getAuditDate() != null ? bo.getAuditDate() : new Date();
if ("PASS".equals(auditResult)) {
if (bo.getAuditHasDanger() == null) {
throw new ServiceException("请选择是否存在隐患");
}
if (bo.getAuditHasDanger() == 1L && bo.getEvaluatorId() == null) {
throw new ServiceException("请选择评估人");
}
baseMapper.update(
null,
Wrappers.<HotHiddenDangerInspection>lambdaUpdate()
.eq(HotHiddenDangerInspection::getId, original.getId())
.set(HotHiddenDangerInspection::getApproverId, bo.getApproverId())
.set(HotHiddenDangerInspection::getApproverName, bo.getApproverName())
.set(HotHiddenDangerInspection::getAuditDate, auditDate)
.set(HotHiddenDangerInspection::getAuditConclusion, auditConclusion)
.set(HotHiddenDangerInspection::getApproverSignImgUrl, bo.getApproverSignImgUrl())
.set(HotHiddenDangerInspection::getAuditHasDanger, bo.getAuditHasDanger())
.set(HotHiddenDangerInspection::getEvaluatorId, bo.getEvaluatorId())
.set(HotHiddenDangerInspection::getEvaluatorName, bo.getEvaluatorName())
.set(HotHiddenDangerInspection::getAuditResult, "PASS")
);
flowService.audit(
bo.getTaskId(),
true,
auditConclusion,
LoginHelper.getBusinessUserId()
);
return;
}
if (StringUtils.isBlank(auditConclusion)) {
throw new ServiceException("审核不通过原因不能为空");
}
long nextRejectCount = original.getRejectCount() == null ? 1L : original.getRejectCount() + 1L;
baseMapper.update(
null,
Wrappers.<HotHiddenDangerInspection>lambdaUpdate()
.eq(HotHiddenDangerInspection::getId, original.getId())
.set(HotHiddenDangerInspection::getApproverId, bo.getApproverId())
.set(HotHiddenDangerInspection::getApproverName, bo.getApproverName())
.set(HotHiddenDangerInspection::getStatus, 10L)
.set(HotHiddenDangerInspection::getFlowStatus, "REJECTED")
.set(HotHiddenDangerInspection::getAuditDate, auditDate)
.set(HotHiddenDangerInspection::getAuditConclusion, auditConclusion)
.set(HotHiddenDangerInspection::getApproverSignImgUrl, bo.getApproverSignImgUrl())
.set(HotHiddenDangerInspection::getAuditHasDanger, null)
.set(HotHiddenDangerInspection::getEvaluatorId, null)
.set(HotHiddenDangerInspection::getEvaluatorName, null)
.set(HotHiddenDangerInspection::getAuditResult, "REJECT")
.set(HotHiddenDangerInspection::getRejectCount, nextRejectCount)
.set(HotHiddenDangerInspection::getLastRejectReason, auditConclusion)
.set(HotHiddenDangerInspection::getLastRejectTime, auditDate)
.set(HotHiddenDangerInspection::getLastRejectById, bo.getApproverId())
.set(HotHiddenDangerInspection::getLastRejectByName, bo.getApproverName())
);
bo.setAuditConclusion(auditConclusion);
saveRejectHistory(original, bo, nextRejectCount, auditDate);
flowService.audit(
bo.getTaskId(),
false,
auditConclusion,
LoginHelper.getBusinessUserId()
);
}
private boolean shouldStartAuditFlow(HotHiddenDangerInspection original) {
if (original == null || StringUtils.isNotBlank(original.getInstanceId())) {
return false;
}
Long status = original.getStatus();
return status == null || status == 0L || status == 1L || status == 10L || "REJECTED".equalsIgnoreCase(original.getFlowStatus());
}
private boolean shouldResumeRejectedFlow(HotHiddenDangerInspection original) {
if (original == null || StringUtils.isBlank(original.getInstanceId())) {
return false;
}
Long status = original.getStatus();
return status != null && status == 10L && "REJECTED".equalsIgnoreCase(original.getFlowStatus());
}
private void resumeRejectedFlow(HotHiddenDangerInspection original) {
String currentUserId = LoginHelper.getBusinessUserId();
String taskId = flowService.getTaskId(original.getInstanceId(), currentUserId);
if (StringUtils.isBlank(taskId)) {
taskId = flowService.getTaskIdByBusinessId("HIDDEN_DANGER_CHECK", String.valueOf(original.getId()), currentUserId);
}
if (StringUtils.isBlank(taskId)) {
taskId = flowService.getCurrentTaskIdByBusinessId("HIDDEN_DANGER_CHECK", String.valueOf(original.getId()));
}
if (StringUtils.isBlank(taskId)) {
throw new ServiceException("当前记录未找到待处理任务,无法重新提交");
}
String submitOperatorId = currentUserId;
var task = flowService.getTaskById(taskId);
if (task == null) {
throw new ServiceException("当前记录未找到待处理任务,无法重新提交");
}
if (!StringUtils.equals(task.getApproverId(), currentUserId)) {
if (!DriverLoginContextHelper.isDriverPort() && !canResubmitRejectedRecord(original, currentUserId)) {
throw new ServiceException("当前用户无权重新提交该记录");
}
submitOperatorId = task.getApproverId();
}
baseMapper.update(
null,
Wrappers.<HotHiddenDangerInspection>lambdaUpdate()
.eq(HotHiddenDangerInspection::getId, original.getId())
.set(HotHiddenDangerInspection::getStatus, 2L)
.set(HotHiddenDangerInspection::getFlowStatus, "SUBMITTED")
.set(HotHiddenDangerInspection::getAuditDate, null)
.set(HotHiddenDangerInspection::getAuditConclusion, null)
.set(HotHiddenDangerInspection::getApproverSignImgUrl, null)
.set(HotHiddenDangerInspection::getAuditHasDanger, null)
.set(HotHiddenDangerInspection::getAuditResult, null)
.set(HotHiddenDangerInspection::getEvaluatorId, null)
.set(HotHiddenDangerInspection::getEvaluatorName, null)
);
flowService.audit(taskId, true, "重新提交", submitOperatorId);
}
private boolean canResubmitRejectedRecord(HotHiddenDangerInspection original, String currentUserId) {
if (original == null || StringUtils.isBlank(currentUserId)) {
return false;
}
if (StringUtils.equals(original.getCheckerId(), currentUserId)) {
return true;
}
if (original.getCreateBy() != null && StringUtils.equals(String.valueOf(original.getCreateBy()), currentUserId)) {
return true;
}
if (DriverLoginContextHelper.isDriverPort()) {
try {
HotDriver currentDriver = DriverLoginContextHelper.getCurrentDriver();
if (currentDriver != null && StringUtils.equals(currentDriver.getId(), original.getCheckerId())) {
return true;
}
if (currentDriver != null && original.getCreateBy() != null
&& StringUtils.equals(currentDriver.getId(), String.valueOf(original.getCreateBy()))) {
return true;
}
} catch (Exception e) {
log.warn("Resolve current driver for hidden danger resubmit failed, inspectionId={}", original.getId(), e);
}
}
return false;
}
private Long buildNextSubmitVersion(HotHiddenDangerInspection original) {
if (original == null || original.getSubmitVersion() == null || original.getSubmitVersion() < 1L) {
return 1L;
}
return original.getSubmitVersion() + 1L;
}
private String normalizeAuditResult(HotHiddenDangerInspectionBo bo) {
if (StringUtils.isNotBlank(bo.getAuditResult())) {
return bo.getAuditResult().trim().toUpperCase();
}
if (bo.getAuditHasDanger() != null && bo.getAuditHasDanger() == 0L) {
return "PASS";
}
return "REJECT";
}
private void saveRejectHistory(HotHiddenDangerInspection original, HotHiddenDangerInspectionBo bo, Long auditRound, Date auditDate) {
HotHiddenDangerInspectionAuditHistory history = new HotHiddenDangerInspectionAuditHistory();
history.setInspectionId(original.getId());
history.setCompanyId(original.getCompanyId());
history.setPlanId(original.getPlanId());
history.setProjectName(original.getProjectName());
history.setProjectId(original.getProjectId());
history.setProjectType(original.getProjectType());
history.setSubmitVersion(original.getSubmitVersion() == null ? 1L : original.getSubmitVersion());
history.setAuditRound(auditRound);
history.setAuditResult("REJECT");
history.setRejectReason(bo.getAuditConclusion());
history.setAuditDate(auditDate);
history.setApproverId(bo.getApproverId());
history.setApproverName(bo.getApproverName());
history.setApproverSignImgUrl(bo.getApproverSignImgUrl());
history.setAuditHasDanger(bo.getAuditHasDanger());
history.setSubmitSnapshotJson(buildSubmitSnapshotJson(original));
history.setCheckContentJson(original.getCheckContentJson());
history.setDangerDesc(original.getDangerDesc());
history.setAttachmentUrl(original.getAttachmentUrl());
history.setSignImgUrl(original.getSignImgUrl());
history.setCheckerId(original.getCheckerId());
history.setCheckerName(original.getCheckerName());
history.setCheckDate(original.getCheckDate());
historyMapper.insert(history);
}
private String buildSubmitSnapshotJson(HotHiddenDangerInspection original) {
Map<String, Object> snapshot = new LinkedHashMap<>();
snapshot.put("inspectionId", original.getId());
snapshot.put("companyId", original.getCompanyId());
snapshot.put("planId", original.getPlanId());
snapshot.put("projectName", original.getProjectName());
snapshot.put("projectId", original.getProjectId());
snapshot.put("projectType", original.getProjectType());
snapshot.put("submitVersion", original.getSubmitVersion());
snapshot.put("checkContentJson", original.getCheckContentJson());
snapshot.put("dangerDesc", original.getDangerDesc());
snapshot.put("attachmentUrl", original.getAttachmentUrl());
snapshot.put("signImgUrl", original.getSignImgUrl());
snapshot.put("checkerId", original.getCheckerId());
snapshot.put("checkerName", original.getCheckerName());
snapshot.put("checkDate", original.getCheckDate());
return JSONUtil.toJsonStr(snapshot);
}
private List<HotHiddenDangerInspectionAuditHistoryVo> queryRejectHistoryList(Long inspectionId) {
return historyMapper.selectVoList(
Wrappers.<HotHiddenDangerInspectionAuditHistory>lambdaQuery()
.eq(HotHiddenDangerInspectionAuditHistory::getInspectionId, inspectionId)
.eq(HotHiddenDangerInspectionAuditHistory::getIsDeleted, 0L)
.orderByDesc(HotHiddenDangerInspectionAuditHistory::getAuditRound)
.orderByDesc(HotHiddenDangerInspectionAuditHistory::getCreateTime)
.orderByDesc(HotHiddenDangerInspectionAuditHistory::getId)
);
}
private void fillTaskId(HotHiddenDangerInspectionVo vo, String userId) {
if (vo == null || StringUtils.isBlank(userId)) {
return;
}
String taskId = null;
if (StringUtils.isNotBlank(vo.getInstanceId())) {
taskId = flowService.getTaskId(vo.getInstanceId(), userId);
}
if (StringUtils.isBlank(taskId) && vo.getId() != null) {
taskId = flowService.getTaskIdByBusinessId("HIDDEN_DANGER_CHECK", String.valueOf(vo.getId()), userId);
}
if (StringUtils.isBlank(taskId) && vo.getId() != null) {
taskId = flowService.getCurrentTaskIdByBusinessId("HIDDEN_DANGER_CHECK", String.valueOf(vo.getId()));
}
vo.setTaskId(taskId);
}
private HotHiddenDangerPlan getPlanByInspectionOrThrow(HotHiddenDangerInspection inspection) {
if (inspection == null || inspection.getPlanId() == null) {
throw new ServiceException("排查计划不存在");
}
HotHiddenDangerPlan plan = planMapper.selectOne(
Wrappers.<HotHiddenDangerPlan>lambdaQuery()
.eq(HotHiddenDangerPlan::getId, inspection.getPlanId())
.eq(HotHiddenDangerPlan::getIsDeleted, 0L)
.last("LIMIT 1")
);
if (plan == null) {
throw new ServiceException("排查计划不存在");
}
return plan;
}
/**

View File

@@ -2,6 +2,8 @@ package com.hotwj.platform.securityManagement.hiddenDangerPlan.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.bo.HotHiddenDangerPlanBo;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.bo.HotHiddenDangerPlanRecheckBo;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.bo.HotHiddenDangerPlanRecheckCloseBo;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.vo.HotHiddenDangerPlanVo;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.service.IHotHiddenDangerPlanService;
import io.swagger.v3.oas.annotations.Operation;
@@ -130,6 +132,39 @@ public class HotHiddenDangerPlanController extends BaseController {
return toAjax(hotHiddenDangerPlanService.updateByBo(bo));
}
/**
* 开启补查
*/
@Log(title = "隐患排查补查", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PostMapping("/recheck/open")
@Operation(summary = "开启隐患排查补查")
public R<Void> openRecheck(@Validated @RequestBody HotHiddenDangerPlanRecheckBo bo) {
return toAjax(hotHiddenDangerPlanService.openRecheck(bo.getPlanId(), bo.getRecheckStartTime(), bo.getRecheckEndTime()));
}
/**
* 修改补查
*/
@Log(title = "隐患排查补查", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/recheck/update")
@Operation(summary = "修改隐患排查补查")
public R<Void> updateRecheck(@Validated @RequestBody HotHiddenDangerPlanRecheckBo bo) {
return toAjax(hotHiddenDangerPlanService.updateRecheck(bo.getPlanId(), bo.getRecheckStartTime(), bo.getRecheckEndTime()));
}
/**
* 关闭补查
*/
@Log(title = "隐患排查补查", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/recheck/close")
@Operation(summary = "关闭隐患排查补查")
public R<Void> closeRecheck(@Validated @RequestBody HotHiddenDangerPlanRecheckCloseBo bo) {
return toAjax(hotHiddenDangerPlanService.closeRecheck(bo.getPlanId()));
}
/**
* 删除隐患排查
*

View File

@@ -60,6 +60,21 @@ public class HotHiddenDangerPlan extends BaseEntity {
*/
private Date endDate;
/**
* 是否开启补查0否 1是
*/
private Long isRecheck;
/**
* 补查开始时间
*/
private Date recheckStartTime;
/**
* 补查结束时间
*/
private Date recheckEndTime;
/**
* 进度 0=待处理 1=审核中 2=评估 3=治理 4=复查 5=完结
*/

View File

@@ -63,6 +63,21 @@ public class HotHiddenDangerPlanBo extends BaseEntity {
@NotNull(message = "结束日期不能为空", groups = {AddGroup.class, EditGroup.class})
private Date endDate;
/**
* 是否开启补查0否 1是
*/
private Long isRecheck;
/**
* 补查开始时间
*/
private Date recheckStartTime;
/**
* 补查结束时间
*/
private Date recheckEndTime;
/**
* 进度 0=待处理 1=审核中 2=评估 3=治理 4=复查 5=完结
*/

View File

@@ -0,0 +1,37 @@
package com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Date;
/**
* 隐患排查补查开启/修改请求对象
*
* @author shihongwei
* @date 2026-06-03
*/
@Data
public class HotHiddenDangerPlanRecheckBo {
/**
* 计划ID
*/
@NotNull(message = "计划ID不能为空")
private Long planId;
/**
* 补查开始时间
*/
@NotNull(message = "补查开始时间不能为空")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date recheckStartTime;
/**
* 补查结束时间
*/
@NotNull(message = "补查结束时间不能为空")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date recheckEndTime;
}

View File

@@ -0,0 +1,20 @@
package com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.bo;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 隐患排查补查关闭请求对象
*
* @author shihongwei
* @date 2026-06-03
*/
@Data
public class HotHiddenDangerPlanRecheckCloseBo {
/**
* 计划ID
*/
@NotNull(message = "计划ID不能为空")
private Long planId;
}

View File

@@ -67,6 +67,24 @@ public class HotHiddenDangerPlanVo implements Serializable {
@ExcelProperty(value = "结束日期")
private Date endDate;
/**
* 是否开启补查0否 1是
*/
@ExcelProperty(value = "是否开启补查")
private Long isRecheck;
/**
* 补查开始时间
*/
@ExcelProperty(value = "补查开始时间")
private Date recheckStartTime;
/**
* 补查结束时间
*/
@ExcelProperty(value = "补查结束时间")
private Date recheckEndTime;
/**
* 进度 0=待处理 1=审核中 2=评估 3=治理 4=复查 5=完结
*/
@@ -97,5 +115,23 @@ public class HotHiddenDangerPlanVo implements Serializable {
@ExcelProperty(value = "计划周期Key")
private String planDateKey;
/**
* 已完成数
*/
@ExcelProperty(value = "已完成数")
private Long completedCount;
/**
* 未完成数
*/
@ExcelProperty(value = "未完成数")
private Long pendingCount;
/**
* 待审核人数
*/
@ExcelProperty(value = "待审核人数")
private Long pendingAuditCount;
}

View File

@@ -2,9 +2,15 @@ package com.hotwj.platform.securityManagement.hiddenDangerPlan.mapper;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.HotHiddenDangerPlan;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.vo.HotHiddenDangerPlanVo;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import java.util.List;
/**
* 隐患排查Mapper接口
*
@@ -14,4 +20,8 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
@Mapper
public interface HotHiddenDangerPlanMapper extends BaseMapperPlus<HotHiddenDangerPlan, HotHiddenDangerPlanVo> {
Page<HotHiddenDangerPlanVo> queryPageList(Page<HotHiddenDangerPlanVo> page,
@Param(Constants.WRAPPER) Wrapper<HotHiddenDangerPlan> queryWrapper);
List<HotHiddenDangerPlanVo> queryList(@Param(Constants.WRAPPER) Wrapper<HotHiddenDangerPlan> queryWrapper);
}

View File

@@ -0,0 +1,63 @@
package com.hotwj.platform.securityManagement.hiddenDangerPlan.service;
import com.hotwj.platform.securityManagement.hiddenDangerPlan.domain.HotHiddenDangerPlan;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 隐患排查计划时间窗口判定
*
* @author shihongwei
* @date 2026-06-03
*/
@Component
public class HiddenDangerPlanTimeWindowHelper {
/**
* 是否在原始时间窗口内
*/
public boolean isInOriginalWindow(Date now, HotHiddenDangerPlan plan) {
if (now == null || plan == null || plan.getStartDate() == null || plan.getEndDate() == null) {
return false;
}
return !now.before(plan.getStartDate()) && !now.after(plan.getEndDate());
}
/**
* 是否在补查时间窗口内
*/
public boolean isInRecheckWindow(Date now, HotHiddenDangerPlan plan) {
if (now == null || plan == null) {
return false;
}
if (!Long.valueOf(1L).equals(plan.getIsRecheck())) {
return false;
}
if (plan.getRecheckStartTime() == null || plan.getRecheckEndTime() == null) {
return false;
}
return !now.before(plan.getRecheckStartTime()) && !now.after(plan.getRecheckEndTime());
}
/**
* 是否在有效时间窗口内
*/
public boolean isInEffectiveWindow(Date now, HotHiddenDangerPlan plan) {
return isInOriginalWindow(now, plan) || isInRecheckWindow(now, plan);
}
/**
* 是否可以在有效时间窗口内处理
*/
public boolean canProcess(Date now, HotHiddenDangerPlan plan) {
return isInEffectiveWindow(now, plan);
}
/**
* 是否应该在司机端展示
*/
public boolean shouldDisplayToDriver(Date now, HotHiddenDangerPlan plan) {
return isInEffectiveWindow(now, plan);
}
}

View File

@@ -69,6 +69,34 @@ public interface IHotHiddenDangerPlanService {
*/
void generatePlan(Long companyId);
/**
* 开启补查
*
* @param planId 计划ID
* @param recheckStartTime 补查开始时间
* @param recheckEndTime 补查结束时间
* @return 是否成功
*/
Boolean openRecheck(Long planId, java.util.Date recheckStartTime, java.util.Date recheckEndTime);
/**
* 修改补查
*
* @param planId 计划ID
* @param recheckStartTime 补查开始时间
* @param recheckEndTime 补查结束时间
* @return 是否成功
*/
Boolean updateRecheck(Long planId, java.util.Date recheckStartTime, java.util.Date recheckEndTime);
/**
* 关闭补查
*
* @param planId 计划ID
* @return 是否成功
*/
Boolean closeRecheck(Long planId);
/**
* 校验并批量删除隐患排查信息
*

View File

@@ -13,6 +13,7 @@ import com.hotwj.platform.config.hiddenDangerCheckConfig.service.IHotHiddenDange
import com.hotwj.platform.driverManagement.driver.domain.bo.HotDriverBo;
import com.hotwj.platform.driverManagement.driver.domain.vo.HotDriverVo;
import com.hotwj.platform.driverManagement.driver.service.IHotDriverService;
import com.hotwj.platform.common.helper.DriverLoginContextHelper;
import com.hotwj.platform.noticeManagerment.systemNotification.domain.bo.HotSystemNotificationGroupBo;
import com.hotwj.platform.noticeManagerment.systemNotification.service.IHotSystemNotificationService;
import com.hotwj.platform.resourceManagement.company.domain.vo.SysCompanyVo;
@@ -33,6 +34,7 @@ import com.hotwj.platform.securityManagement.hiddenDangerPlan.mapper.HotHiddenDa
import com.hotwj.platform.securityManagement.hiddenDangerPlan.service.IHotHiddenDangerPlanService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -43,6 +45,7 @@ import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.IsoFields;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -90,7 +93,7 @@ public class HotHiddenDangerPlanServiceImpl implements IHotHiddenDangerPlanServi
@Override
public TableDataInfo<HotHiddenDangerPlanVo> queryPageList(HotHiddenDangerPlanBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<HotHiddenDangerPlan> lqw = buildQueryWrapper(bo);
Page<HotHiddenDangerPlanVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
Page<HotHiddenDangerPlanVo> result = baseMapper.queryPageList(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
@@ -103,7 +106,7 @@ public class HotHiddenDangerPlanServiceImpl implements IHotHiddenDangerPlanServi
@Override
public List<HotHiddenDangerPlanVo> queryList(HotHiddenDangerPlanBo bo) {
LambdaQueryWrapper<HotHiddenDangerPlan> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
return baseMapper.queryList(lqw);
}
/**
@@ -183,6 +186,45 @@ public class HotHiddenDangerPlanServiceImpl implements IHotHiddenDangerPlanServi
processCompanyPlan(companyConfig, checkConfigsMap);
}
@Override
public Boolean openRecheck(Long planId, Date recheckStartTime, Date recheckEndTime) {
HotHiddenDangerPlan plan = getPlanOrThrow(planId);
validateRecheckTime(recheckStartTime, recheckEndTime);
return baseMapper.update(
null,
Wrappers.<HotHiddenDangerPlan>lambdaUpdate()
.eq(HotHiddenDangerPlan::getId, plan.getId())
.set(HotHiddenDangerPlan::getIsRecheck, 1L)
.set(HotHiddenDangerPlan::getRecheckStartTime, recheckStartTime)
.set(HotHiddenDangerPlan::getRecheckEndTime, recheckEndTime)
) > 0;
}
@Override
public Boolean updateRecheck(Long planId, Date recheckStartTime, Date recheckEndTime) {
HotHiddenDangerPlan plan = getPlanOrThrow(planId);
validateRecheckTime(recheckStartTime, recheckEndTime);
return baseMapper.update(
null,
Wrappers.<HotHiddenDangerPlan>lambdaUpdate()
.eq(HotHiddenDangerPlan::getId, plan.getId())
.set(HotHiddenDangerPlan::getIsRecheck, 1L)
.set(HotHiddenDangerPlan::getRecheckStartTime, recheckStartTime)
.set(HotHiddenDangerPlan::getRecheckEndTime, recheckEndTime)
) > 0;
}
@Override
public Boolean closeRecheck(Long planId) {
HotHiddenDangerPlan plan = getPlanOrThrow(planId);
return baseMapper.update(
null,
Wrappers.<HotHiddenDangerPlan>lambdaUpdate()
.eq(HotHiddenDangerPlan::getId, plan.getId())
.set(HotHiddenDangerPlan::getIsRecheck, 0L)
) > 0;
}
/**
* 处理单个公司的排查计划
*/
@@ -299,7 +341,7 @@ public class HotHiddenDangerPlanServiceImpl implements IHotHiddenDangerPlanServi
lqw.eq(HotHiddenDangerPlan::getCompanyId, companyId);
lqw.eq(HotHiddenDangerPlan::getIsDeleted, 0L);
lqw.eq(HotHiddenDangerPlan::getPlanDateKey, timeInfo.getPlanDateKey());
lqw.orderByDesc(HotHiddenDangerPlan::getCreateTime);
lqw.orderByDesc(HotHiddenDangerPlan::getCreateTime, HotHiddenDangerPlan::getId);
lqw.last("LIMIT 1");
return baseMapper.selectOne(lqw);
}
@@ -511,19 +553,39 @@ public class HotHiddenDangerPlanServiceImpl implements IHotHiddenDangerPlanServi
}
private LambdaQueryWrapper<HotHiddenDangerPlan> buildQueryWrapper(HotHiddenDangerPlanBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotHiddenDangerPlan> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotHiddenDangerPlan::getCreateTime);
lqw.orderByDesc(HotHiddenDangerPlan::getCreateTime, HotHiddenDangerPlan::getId);
lqw.eq(bo.getCompanyId() != null, HotHiddenDangerPlan::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getInspectId() != null, HotHiddenDangerPlan::getInspectId, bo.getInspectId());
lqw.like(StringUtils.isNotBlank(bo.getPlanName()), HotHiddenDangerPlan::getPlanName, bo.getPlanName());
lqw.eq(bo.getStartDate() != null, HotHiddenDangerPlan::getStartDate, bo.getStartDate());
lqw.eq(bo.getEndDate() != null, HotHiddenDangerPlan::getEndDate, bo.getEndDate());
lqw.eq(bo.getIsRecheck() != null, HotHiddenDangerPlan::getIsRecheck, bo.getIsRecheck());
lqw.eq(bo.getProgress() != null, HotHiddenDangerPlan::getProgress, bo.getProgress());
lqw.eq(bo.getIsDeleted() != null, HotHiddenDangerPlan::getIsDeleted, bo.getIsDeleted());
if (shouldLimitToEffectiveWindow()) {
Date now = new Date();
lqw.and(wrapper -> wrapper
.and(original -> original
.le(HotHiddenDangerPlan::getStartDate, now)
.ge(HotHiddenDangerPlan::getEndDate, now))
.or(recheck -> recheck
.eq(HotHiddenDangerPlan::getIsRecheck, 1L)
.le(HotHiddenDangerPlan::getRecheckStartTime, now)
.ge(HotHiddenDangerPlan::getRecheckEndTime, now))
);
}
return lqw;
}
private boolean shouldLimitToEffectiveWindow() {
try {
return DriverLoginContextHelper.isDriverPort();
} catch (Exception e) {
return false;
}
}
/**
* 新增隐患排查
*
@@ -626,6 +688,34 @@ public class HotHiddenDangerPlanServiceImpl implements IHotHiddenDangerPlanServi
//TODO 做一些数据校验,如唯一约束
}
private HotHiddenDangerPlan getPlanOrThrow(Long planId) {
if (planId == null) {
throw new ServiceException("计划ID不能为空");
}
HotHiddenDangerPlan plan = baseMapper.selectOne(
Wrappers.<HotHiddenDangerPlan>lambdaQuery()
.eq(HotHiddenDangerPlan::getId, planId)
.eq(HotHiddenDangerPlan::getIsDeleted, 0L)
.last("LIMIT 1")
);
if (plan == null) {
throw new ServiceException("排查计划不存在");
}
return plan;
}
private void validateRecheckTime(Date recheckStartTime, Date recheckEndTime) {
if (recheckStartTime == null) {
throw new ServiceException("补查开始时间不能为空");
}
if (recheckEndTime == null) {
throw new ServiceException("补查结束时间不能为空");
}
if (!recheckEndTime.after(recheckStartTime)) {
throw new ServiceException("补查结束时间必须晚于补查开始时间");
}
}
/**
* 校验并批量删除隐患排查信息
*

View File

@@ -88,7 +88,7 @@ public class HotTrainingServiceImpl implements IHotTrainingService {
private LambdaQueryWrapper<HotTraining> buildQueryWrapper(HotTrainingBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotTraining> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotTraining::getCreateTime);
lqw.orderByDesc(HotTraining::getCreateTime, HotTraining::getId);
lqw.eq(bo.getCompanyId() != null, HotTraining::getCompanyId, bo.getCompanyId());
lqw.like(StringUtils.isNotBlank(bo.getPlanName()), HotTraining::getPlanName, bo.getPlanName());
lqw.eq(StringUtils.isNotBlank(bo.getPersonType()), HotTraining::getPersonType, bo.getPersonType());

View File

@@ -0,0 +1,90 @@
package com.hotwj.platform.securityManagement.training.support;
import com.hotwj.platform.config.companyBasicConfig.domain.bo.HotCompanyBasicConfigBo;
import com.hotwj.platform.config.companyBasicConfig.domain.vo.HotCompanyBasicConfigVo;
import com.hotwj.platform.config.companyBasicConfig.service.IHotCompanyBasicConfigService;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
@Component
@RequiredArgsConstructor
public class StudyForbidPeriodSupport {
public static final String FORBID_STUDY_MESSAGE = "当前处于禁学时间";
private static final Long STATUS_ENABLED = 1L;
private static final DateTimeFormatter TIME_FORMATTER_MINUTE = DateTimeFormatter.ofPattern("H:mm");
private static final DateTimeFormatter TIME_FORMATTER_SECOND = DateTimeFormatter.ofPattern("H:mm:ss");
private final IHotCompanyBasicConfigService companyBasicConfigService;
public void checkStudyAllowed(Long companyId) {
String forbidMessage = getForbidStudyMessage(companyId);
if (StringUtils.isNotBlank(forbidMessage)) {
throw new ServiceException(forbidMessage);
}
}
public String getForbidStudyMessage(Long companyId) {
if (companyId == null) {
return null;
}
HotCompanyBasicConfigVo config = getCompanyBasicConfig(companyId);
if (config == null || !STATUS_ENABLED.equals(config.getForbidPeriodEnabled())) {
return null;
}
if (!isNowInForbidPeriod(config.getForbidStartTime(), config.getForbidEndTime())) {
return null;
}
return FORBID_STUDY_MESSAGE;
}
private HotCompanyBasicConfigVo getCompanyBasicConfig(Long companyId) {
HotCompanyBasicConfigBo bo = new HotCompanyBasicConfigBo();
bo.setCompanyId(companyId);
bo.setIsDeleted(0L);
List<HotCompanyBasicConfigVo> configs = companyBasicConfigService.queryList(bo);
if (configs == null || configs.isEmpty()) {
return null;
}
return configs.get(0);
}
private boolean isNowInForbidPeriod(String forbidStartTime, String forbidEndTime) {
LocalTime startTime = parseTime(forbidStartTime);
LocalTime endTime = parseTime(forbidEndTime);
if (startTime == null || endTime == null) {
return false;
}
LocalTime now = LocalTime.now();
if (startTime.equals(endTime)) {
return true;
}
if (startTime.isBefore(endTime)) {
return !now.isBefore(startTime) && !now.isAfter(endTime);
}
return !now.isBefore(startTime) || !now.isAfter(endTime);
}
private LocalTime parseTime(String value) {
if (StringUtils.isBlank(value)) {
return null;
}
try {
return LocalTime.parse(value, TIME_FORMATTER_SECOND);
} catch (DateTimeParseException ignored) {
}
try {
return LocalTime.parse(value, TIME_FORMATTER_MINUTE);
} catch (DateTimeParseException ignored) {
}
return null;
}
}

View File

@@ -15,6 +15,7 @@ import com.hotwj.platform.securityManagement.trainingCourseRecord.service.IHotTr
import com.hotwj.platform.securityManagement.trainingParticipant.domain.bo.HotTrainingParticipantBo;
import com.hotwj.platform.securityManagement.trainingParticipant.domain.vo.HotTrainingParticipantVo;
import com.hotwj.platform.securityManagement.trainingParticipant.service.IHotTrainingParticipantService;
import com.hotwj.platform.securityManagement.training.support.StudyForbidPeriodSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -58,6 +59,7 @@ public class HotTrainingCourseConfigController extends BaseController {
private final IHotCourseResourceService hotCourseResourceService;
private final IHotTrainingParticipantService hotTrainingParticipantService;
private final IHotTrainingService hotTrainingService;
private final StudyForbidPeriodSupport studyForbidPeriodSupport;
/**
* 查询企业课程配置列表
@@ -66,7 +68,6 @@ public class HotTrainingCourseConfigController extends BaseController {
@GetMapping("/list")
@Operation(summary = "分页查询企业课程配置列表")
public TableDataInfo<HotTrainingCourseConfigVo> list(HotTrainingCourseConfigBo bo, PageQuery pageQuery) {
// TODO
if (!Boolean.TRUE.equals(bo.getSkipIntercept()) && bo.getTrainingId() != null && StringUtils.isNotBlank(bo.getUserId())
&& !isDriverTrainingAccessible(bo.getCompanyId(), bo.getTrainingId(), bo.getUserId())) {
TableDataInfo<HotTrainingCourseConfigVo> result = TableDataInfo.build(Collections.emptyList());
@@ -138,9 +139,17 @@ public class HotTrainingCourseConfigController extends BaseController {
public R<Map<String, Object>> trainingStudyDetail(
@RequestParam Long companyId,
@RequestParam Long trainingId,
@RequestParam String userId
@RequestParam String userId,
@RequestParam(required = false, defaultValue = "false") Boolean forceView,
@RequestParam(required = false, defaultValue = "false") Boolean checkForbidStudy
) {
if (!isDriverTrainingAccessible(companyId, trainingId, userId)) {
if (Boolean.TRUE.equals(checkForbidStudy)) {
String forbidMessage = studyForbidPeriodSupport.getForbidStudyMessage(companyId);
if (StringUtils.isNotBlank(forbidMessage)) {
return R.fail(forbidMessage);
}
}
if (!Boolean.TRUE.equals(forceView) && !isDriverTrainingAccessible(companyId, trainingId, userId)) {
return R.fail("当前学习计划已关闭或已过期");
}
HotTrainingVo training = hotTrainingService.queryById(trainingId);

View File

@@ -79,7 +79,7 @@ public class HotTrainingCourseConfigServiceImpl implements IHotTrainingCourseCon
private LambdaQueryWrapper<HotTrainingCourseConfig> buildQueryWrapper(HotTrainingCourseConfigBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotTrainingCourseConfig> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(HotTrainingCourseConfig::getSortNo);
lqw.orderByAsc(HotTrainingCourseConfig::getSortNo, HotTrainingCourseConfig::getId);
lqw.eq(bo.getCompanyId() != null, HotTrainingCourseConfig::getCompanyId, bo.getCompanyId());
lqw.like(StringUtils.isNotBlank(bo.getCourseName()), HotTrainingCourseConfig::getCourseName, bo.getCourseName());
lqw.eq(bo.getCourseId() != null, HotTrainingCourseConfig::getCourseId, bo.getCourseId());
@@ -107,7 +107,7 @@ public class HotTrainingCourseConfigServiceImpl implements IHotTrainingCourseCon
.select(HotTrainingCourseConfig::getSortNo)
.eq(HotTrainingCourseConfig::getCompanyId, bo.getCompanyId())
.eq(bo.getTrainingId() != null, HotTrainingCourseConfig::getTrainingId, bo.getTrainingId())
.orderByDesc(HotTrainingCourseConfig::getSortNo)
.orderByDesc(HotTrainingCourseConfig::getSortNo, HotTrainingCourseConfig::getId)
.last("limit 1")
);
long max = (maxSort != null && maxSort.getSortNo() != null) ? maxSort.getSortNo() : 0L;

View File

@@ -103,6 +103,11 @@ public class HotTrainingCourseRecordBo extends BaseEntity {
*/
private Long hourPackageUsedHours;
/**
* 是否需要校验禁学时间false=否 true=是
*/
private Boolean checkForbidStudy;
/**
* 创建者姓名
*/

View File

@@ -18,6 +18,7 @@ import com.hotwj.platform.securityManagement.trainingCourseRecord.domain.vo.HotT
import com.hotwj.platform.securityManagement.trainingCourseRecord.domain.vo.PersonalAnnualRecordCourseVo;
import com.hotwj.platform.securityManagement.trainingCourseRecord.mapper.HotTrainingCourseRecordMapper;
import com.hotwj.platform.securityManagement.trainingCourseRecord.service.IHotTrainingCourseRecordService;
import com.hotwj.platform.securityManagement.training.support.StudyForbidPeriodSupport;
import com.hotwj.platform.securityManagement.trainingProgress.service.TrainingProgressService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -55,6 +56,7 @@ public class HotTrainingCourseRecordServiceImpl implements IHotTrainingCourseRec
private final HotHourUsageDetailMapper hotHourUsageDetailMapper;
private final HotTrainingMapper hotTrainingMapper;
private final HotTrainingCourseConfigMapper hotTrainingCourseConfigMapper;
private final StudyForbidPeriodSupport studyForbidPeriodSupport;
/**
* 查询培训人员课程学习记录
@@ -108,6 +110,7 @@ public class HotTrainingCourseRecordServiceImpl implements IHotTrainingCourseRec
lqw.eq(bo.getTrainingCourseId() != null, HotTrainingCourseRecord::getTrainingCourseId, bo.getTrainingCourseId());
lqw.orderByDesc(HotTrainingCourseRecord::getCompleteTime);
lqw.orderByDesc(HotTrainingCourseRecord::getCreateTime);
lqw.orderByDesc(HotTrainingCourseRecord::getId);
Page<HotTrainingCourseRecordVo> page = baseMapper.selectVoPage(new Page<>(1, 1), lqw);
List<HotTrainingCourseRecordVo> records = page.getRecords();
if (records == null || records.isEmpty()) {
@@ -124,7 +127,7 @@ public class HotTrainingCourseRecordServiceImpl implements IHotTrainingCourseRec
private LambdaQueryWrapper<HotTrainingCourseRecord> buildQueryWrapper(HotTrainingCourseRecordBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotTrainingCourseRecord> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotTrainingCourseRecord::getCreateTime);
lqw.orderByDesc(HotTrainingCourseRecord::getCreateTime, HotTrainingCourseRecord::getId);
lqw.eq(bo.getCompanyId() != null, HotTrainingCourseRecord::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getTrainingId() != null, HotTrainingCourseRecord::getTrainingId, bo.getTrainingId());
lqw.eq(StringUtils.isNotBlank(bo.getUserId()), HotTrainingCourseRecord::getUserId, bo.getUserId());
@@ -152,6 +155,9 @@ public class HotTrainingCourseRecordServiceImpl implements IHotTrainingCourseRec
@Override
@Transactional(rollbackFor = Exception.class)
public Long insertByBo(HotTrainingCourseRecordBo bo) {
if (Boolean.TRUE.equals(bo.getCheckForbidStudy())) {
studyForbidPeriodSupport.checkStudyAllowed(bo.getCompanyId());
}
initHourPackageUsageFieldsForInsert(bo);
HotTrainingCourseRecord add = MapstructUtils.convert(bo, HotTrainingCourseRecord.class);
validEntityBeforeSave(add);
@@ -178,8 +184,12 @@ public class HotTrainingCourseRecordServiceImpl implements IHotTrainingCourseRec
public Boolean updateByBo(HotTrainingCourseRecordBo bo) {
HotTrainingCourseRecord update = MapstructUtils.convert(bo, HotTrainingCourseRecord.class);
HotTrainingCourseRecord before = null;
Long companyId = bo.getCompanyId();
if (bo.getId() != null) {
before = baseMapper.selectById(bo.getId());
if (companyId == null && before != null) {
companyId = before.getCompanyId();
}
if (before != null && before.getProgressRate() != null && update.getProgressRate() != null) {
// 仅操作单条记录时,确保视频进度单调不下降
if (update.getProgressRate().compareTo(before.getProgressRate()) < 0) {
@@ -193,6 +203,9 @@ public class HotTrainingCourseRecordServiceImpl implements IHotTrainingCourseRec
}
}
}
if (Boolean.TRUE.equals(bo.getCheckForbidStudy())) {
studyForbidPeriodSupport.checkStudyAllowed(companyId);
}
initHourPackageUsageFieldsForUpdate(update, before);
validEntityBeforeSave(update);
boolean ok = baseMapper.updateById(update) > 0;

View File

@@ -80,8 +80,7 @@ public class HotTrainingParticipantServiceImpl implements IHotTrainingParticipan
Page<HotTrainingParticipantVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
if (result.getRecords() != null) {
result.getRecords().forEach(this::fillCanAuditStatus);
// 填充学员信息
result.getRecords().forEach(this::fillDriverInfo);
result.getRecords().forEach(this::fillDriverPhotoImgUrl);
}
return TableDataInfo.build(result);
@@ -127,14 +126,36 @@ public class HotTrainingParticipantServiceImpl implements IHotTrainingParticipan
vo.setCanAudit(canAudit ? 1L : 0L);
}
private void fillDriverInfo(HotTrainingParticipantVo vo) {
private void fillDriverPhotoImgUrl(HotTrainingParticipantVo vo) {
if (vo == null) {
return;
}
HotDriverVo hotDriverVo = driverMapper.selectVoById(vo.getUserId());
if (hotDriverVo != null) {
vo.setDriverPhotoImgUrl(hotDriverVo.getPortraitUrl());
vo.setDriverPhotoImgUrl(resolveStudyPhotoImgUrl(vo.getStudyPhotoOssId()));
}
private String resolveStudyPhotoImgUrl(String studyPhotoOssId) {
if (StringUtils.isBlank(studyPhotoOssId)) {
return null;
}
try {
LinkedHashMap<String, Object> photoMap = JsonUtils.parseObject(
studyPhotoOssId,
new TypeReference<LinkedHashMap<String, Object>>() {
}
);
if (photoMap != null && !photoMap.isEmpty()) {
String latestPhotoOssId = null;
for (Object value : photoMap.values()) {
if (value != null && StringUtils.isNotBlank(String.valueOf(value))) {
latestPhotoOssId = String.valueOf(value);
}
}
return latestPhotoOssId;
}
} catch (Exception e) {
log.warn("解析驾驶员学习拍照图片失败 studyPhotoOssId={} err={}", studyPhotoOssId, e.getMessage());
}
return studyPhotoOssId;
}
private LambdaQueryWrapper<HotTrainingParticipant> buildQueryWrapper(HotTrainingParticipantBo bo) {

View File

@@ -162,7 +162,7 @@ public class TrainingProgressService {
.eq(HotTrainingCourseRecord::getUserId, userId)
.in(HotTrainingCourseRecord::getTrainingCourseId, trainingCourseIds)
.eq(HotTrainingCourseRecord::getIsDeleted, 0L)
.orderByDesc(HotTrainingCourseRecord::getCreateTime)
.orderByDesc(HotTrainingCourseRecord::getCreateTime, HotTrainingCourseRecord::getId)
);
if (records == null || records.isEmpty()) {
return Map.of();

View File

@@ -87,7 +87,7 @@ public class HotVehicleEntryExitServiceImpl implements IHotVehicleEntryExitServi
private LambdaQueryWrapper<HotVehicleEntryExit> buildQueryWrapper(HotVehicleEntryExitBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotVehicleEntryExit> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotVehicleEntryExit::getCreateTime);
lqw.orderByDesc(HotVehicleEntryExit::getCreateTime, HotVehicleEntryExit::getId);
lqw.eq(bo.getCompanyId() != null, HotVehicleEntryExit::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getCheckTime() != null, HotVehicleEntryExit::getCheckTime, bo.getCheckTime());
lqw.like(StringUtils.isNotBlank(bo.getCreateByName()), HotVehicleEntryExit::getCreateByName, bo.getCreateByName());

View File

@@ -90,7 +90,7 @@ public class HotVehicleInoutCheckServiceImpl implements IHotVehicleInoutCheckSer
private LambdaQueryWrapper<HotVehicleInoutCheck> buildQueryWrapper(HotVehicleInoutCheckBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotVehicleInoutCheck> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotVehicleInoutCheck::getCheckDate);
lqw.orderByDesc(HotVehicleInoutCheck::getCheckDate, HotVehicleInoutCheck::getId);
lqw.eq(bo.getRecordId() != null, HotVehicleInoutCheck::getRecordId, bo.getRecordId());
lqw.eq(bo.getCompanyId() != null, HotVehicleInoutCheck::getCompanyId, bo.getCompanyId());
lqw.eq(bo.getVehicleId() != null, HotVehicleInoutCheck::getVehicleId, bo.getVehicleId());

View File

@@ -350,7 +350,7 @@ public class HotViolationInfoServiceImpl implements IHotViolationInfoService {
private LambdaQueryWrapper<HotViolationInfo> buildQueryWrapper(HotViolationInfoBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HotViolationInfo> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HotViolationInfo::getOccurTime);
lqw.orderByDesc(HotViolationInfo::getOccurTime, HotViolationInfo::getId);
if (bo.getOccurTime() != null) {
java.util.Calendar calendarStart = java.util.Calendar.getInstance();
calendarStart.setTime(bo.getOccurTime());

View File

@@ -0,0 +1,129 @@
package com.hotwj.platform.system.maintenance.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* 媒体资源元数据修复配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "maintenance.media-resource-repair")
public class MediaResourceMetadataRepairProperties {
/**
* 任务缓存key前缀
*/
private String taskKeyPrefix = "maintenance:media-resource-repair:";
/**
* 临时目录根路径
*/
private String workDir = System.getProperty("java.io.tmpdir") + File.separator + "media-resource-repair";
/**
* ffprobe 命令
*/
private String ffprobeCommand = "ffprobe";
/**
* ffmpeg 命令
*/
private String ffmpegCommand = "ffmpeg";
/**
* 任务状态保留小时数
*/
private Integer taskExpireHours = 24;
/**
* 默认单次处理上限
*/
private Integer defaultLimit = 200;
/**
* 最大单次处理上限
*/
private Integer maxLimit = 500;
/**
* 默认批处理条数
*/
private Integer defaultBatchSize = 20;
/**
* 最大批处理条数
*/
private Integer maxBatchSize = 50;
/**
* 连接超时秒数
*/
private Integer connectTimeoutSeconds = 10;
/**
* 读取超时秒数
*/
private Integer readTimeoutSeconds = 60;
/**
* 单文件最大大小
*/
private Long maxFileSizeBytes = 524288000L;
/**
* HLS 最大分片数
*/
private Integer maxHlsSegments = 500;
/**
* HLS 最大总下载大小
*/
private Long maxHlsTotalSizeBytes = 1073741824L;
/**
* 采样错误条数上限
*/
private Integer maxErrorSamples = 20;
/**
* 播放列表最大大小
*/
private Long maxPlaylistSizeBytes = 2097152L;
/**
* 允许的域名白名单,留空则不限制
*/
private List<String> allowedHosts = new ArrayList<>();
public Duration taskExpireDuration() {
return Duration.ofHours(taskExpireHours == null ? 24L : taskExpireHours.longValue());
}
public boolean isHostAllowed(String host) {
if (host == null || host.isBlank()) {
return false;
}
if (allowedHosts == null || allowedHosts.isEmpty()) {
return true;
}
String normalizedHost = host.toLowerCase(Locale.ROOT);
for (String allowedHost : allowedHosts) {
if (allowedHost == null || allowedHost.isBlank()) {
continue;
}
String normalizedAllowed = allowedHost.toLowerCase(Locale.ROOT).trim();
if (normalizedHost.equals(normalizedAllowed) || normalizedHost.endsWith("." + normalizedAllowed)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,32 @@
package com.hotwj.platform.system.maintenance.controller;
import com.hotwj.platform.system.maintenance.domain.bo.MediaResourceMetadataRepairRequest;
import com.hotwj.platform.system.maintenance.domain.vo.MediaResourceMetadataRepairSubmitVo;
import com.hotwj.platform.system.maintenance.domain.vo.MediaResourceMetadataRepairTaskStatusVo;
import com.hotwj.platform.system.maintenance.service.IMediaResourceMetadataRepairService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.web.core.BaseController;
import org.springframework.web.bind.annotation.*;
/**
* 系统维护-媒体资源元数据修复
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/maintenance/media-resource")
public class MediaResourceMaintenanceController extends BaseController {
private final IMediaResourceMetadataRepairService mediaResourceMetadataRepairService;
@PostMapping("/repair-metadata")
public R<MediaResourceMetadataRepairSubmitVo> repairMetadata(@Valid @RequestBody MediaResourceMetadataRepairRequest request) {
return R.ok(mediaResourceMetadataRepairService.submitRepairTask(request));
}
@GetMapping("/repair-metadata/{taskId}")
public R<MediaResourceMetadataRepairTaskStatusVo> getTaskStatus(@PathVariable String taskId) {
return R.ok(mediaResourceMetadataRepairService.getTaskStatus(taskId));
}
}

View File

@@ -0,0 +1,53 @@
package com.hotwj.platform.system.maintenance.controller;
import com.hotwj.platform.system.maintenance.domain.bo.PhotoEvidenceRepairExecuteRequest;
import com.hotwj.platform.system.maintenance.domain.bo.PhotoEvidenceRepairQueryBo;
import com.hotwj.platform.system.maintenance.domain.bo.PhotoEvidenceRepairTaskRequest;
import com.hotwj.platform.system.maintenance.domain.vo.*;
import com.hotwj.platform.system.maintenance.service.IPhotoEvidenceRepairService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.springframework.web.bind.annotation.*;
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/maintenance/photo-evidence")
public class PhotoEvidenceMaintenanceController extends BaseController {
private final IPhotoEvidenceRepairService photoEvidenceRepairService;
@GetMapping("/list")
@Operation(summary = "分页查询异常图片水印记录")
public TableDataInfo<PhotoEvidenceRepairCandidateVo> list(PhotoEvidenceRepairQueryBo queryBo, PageQuery pageQuery) {
return photoEvidenceRepairService.queryPageList(queryBo, pageQuery);
}
@GetMapping("/preview/{evidenceId}")
@Operation(summary = "生成图片水印修复预览")
public R<PhotoEvidenceRepairPreviewVo> preview(@PathVariable Long evidenceId) {
return R.ok(photoEvidenceRepairService.preview(evidenceId));
}
@PostMapping("/repair")
@Operation(summary = "执行单条图片水印修复")
public R<PhotoEvidenceRepairExecuteVo> repair(@Valid @RequestBody PhotoEvidenceRepairExecuteRequest request) {
return R.ok(photoEvidenceRepairService.repair(request));
}
@PostMapping("/repair-task")
@Operation(summary = "创建批量图片水印修复任务")
public R<PhotoEvidenceRepairSubmitVo> submitRepairTask(@Valid @RequestBody PhotoEvidenceRepairTaskRequest request) {
return R.ok(photoEvidenceRepairService.submitRepairTask(request));
}
@GetMapping("/repair-task/{taskId}")
@Operation(summary = "查询图片水印修复任务状态")
public R<PhotoEvidenceRepairTaskStatusVo> getTaskStatus(@PathVariable String taskId) {
return R.ok(photoEvidenceRepairService.getTaskStatus(taskId));
}
}

View File

@@ -0,0 +1,72 @@
package com.hotwj.platform.system.maintenance.domain.bo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 媒体资源元数据修复请求
*/
@Data
public class MediaResourceMetadataRepairRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 资源类型1=视频 2=音频
*/
private Long mediaType;
/**
* 分类编码
*/
private String categoryCode;
/**
* 公司ID
*/
private Long companyId;
/**
* 指定资源ID列表
*/
private List<Long> resourceIds;
/**
* 是否修复大小
*/
private Boolean repairSize;
/**
* 是否修复时长
*/
private Boolean repairDuration;
/**
* 是否覆盖已有有效值
*/
private Boolean overwriteExisting;
/**
* 是否只处理无效数据
*/
private Boolean onlyInvalid;
/**
* 单次处理上限
*/
private Integer limit;
/**
* 每批处理条数
*/
private Integer batchSize;
/**
* 是否跳过无法解析URL的记录
*/
private Boolean skipUnresolvedUrl;
}

View File

@@ -0,0 +1,17 @@
package com.hotwj.platform.system.maintenance.domain.bo;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class PhotoEvidenceRepairExecuteRequest {
@NotNull(message = "evidenceId不能为空")
private Long evidenceId;
/**
* 前端必须在预览确认后才能发起正式修复。
*/
@NotNull(message = "previewConfirmed不能为空")
private Boolean previewConfirmed;
}

View File

@@ -0,0 +1,34 @@
package com.hotwj.platform.system.maintenance.domain.bo;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class PhotoEvidenceRepairQueryBo {
private Long evidenceId;
private Long watermarkedOssId;
private Long originalOssId;
private Long companyId;
private String bizType;
private String bizId;
private Boolean onlyAbnormal;
private Boolean fileSizeZero;
private Boolean fileSuffixAbnormal;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date beginCreateTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endCreateTime;
}

View File

@@ -0,0 +1,20 @@
package com.hotwj.platform.system.maintenance.domain.bo;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Data
public class PhotoEvidenceRepairTaskRequest {
@NotEmpty(message = "evidenceIds不能为空")
private List<Long> evidenceIds;
/**
* 前端必须确保本次提交的记录都已经过人工预览确认。
*/
@NotNull(message = "previewConfirmed不能为空")
private Boolean previewConfirmed;
}

View File

@@ -0,0 +1,26 @@
package com.hotwj.platform.system.maintenance.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 提交媒体资源元数据修复任务响应
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MediaResourceMetadataRepairSubmitVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String taskId;
private Boolean accepted;
private String message;
}

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