Преглед изворни кода

fix(inspection): 巡检任务 items 持久化 + 扫码设备编码/时间校验

【BUG 1 修】新增/编辑巡检任务后,打卡项目(itemList)丢失
- 原因: insertByBo / updateByBo 之前只更新主表,完全没处理 bo.itemList
- 修法: 主表保存后处理 itemList,update 时先删旧 items 再插新 items(全量替换,简单可靠)

【功能】开启扫码=是时,新增 punch_device_code 字段
- DB 加列 risk_inspection_task.punch_device_code varchar(200)
- entity/Bo/Vo 全链路加 punchDeviceCode
- 移动端扫码内容解析为「设备编码 [分隔符] 时间」格式,后端 verifyQrCode
  * 校验设备编码是否匹配 task.punchDeviceCode
  * 校验时间在 ±24h 窗口内(防止过期二维码复用)
  * 支持空格/竖线/逗号/分号/换行多种分隔符
- 老的整体比对逻辑保留兼容(单段无分隔符)
yangjingjing пре 2 дана
родитељ
комит
5b511fc38e

+ 5 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskInspectionTask.java

@@ -152,6 +152,11 @@ public class RiskInspectionTask extends TenantEntity {
      */
     private String qrCode;
 
+    /**
+     * 打卡设备编码(开启扫码打卡时,二维码中应包含该编码)
+     */
+    private String punchDeviceCode;
+
     /**
      * 审核人ID
      */

+ 10 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskInspectionTaskBo.java

@@ -152,6 +152,16 @@ public class RiskInspectionTaskBo extends BaseEntity {
      */
     private String qrCode;
 
+    /**
+     * 打卡设备编码(开启扫码打卡时必填)
+     */
+    private String punchDeviceCode;
+
+    /**
+     * 巡检项目列表(保存/编辑时一并保存)
+     */
+    private java.util.List<org.dromara.risk.domain.RiskInspectionTaskItem> itemList;
+
     /**
      * 审核人ID
      */

+ 5 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskInspectionTaskVo.java

@@ -192,6 +192,11 @@ public class RiskInspectionTaskVo implements Serializable {
      */
     private String qrCode;
 
+    /**
+     * 打卡设备编码(开启扫码打卡时必填)
+     */
+    private String punchDeviceCode;
+
     /**
      * 审核人ID
      */

+ 67 - 3
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionTaskServiceImpl.java

@@ -12,6 +12,7 @@ import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.risk.domain.RiskInspectionTask;
+import org.dromara.risk.domain.RiskInspectionTaskItem;
 import org.dromara.risk.domain.bo.RiskInspectionTaskBo;
 import org.dromara.risk.domain.RiskInspectionRecord;
 import org.dromara.risk.domain.RiskInspectionTaskItem;
@@ -155,6 +156,10 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
         RiskInspectionTask add = MapstructUtils.convert(bo, RiskInspectionTask.class);
         int row = baseMapper.insert(add);
         bo.setId(add.getId());
+        // 保存巡检项目列表
+        if (bo.getItemList() != null && !bo.getItemList().isEmpty()) {
+            saveTaskItems(add.getId(), bo.getItemList());
+        }
         return row;
     }
 
@@ -167,7 +172,31 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
             bo.setBizType("risk");
         }
         RiskInspectionTask update = MapstructUtils.convert(bo, RiskInspectionTask.class);
-        return baseMapper.updateById(update);
+        int row = baseMapper.updateById(update);
+        // 全量替换巡检项目:先删后插(简单可靠,避免增量同步的复杂性)
+        if (bo.getItemList() != null) {
+            taskItemMapper.delete(Wrappers.<RiskInspectionTaskItem>lambdaQuery()
+                .eq(RiskInspectionTaskItem::getTaskId, bo.getId()));
+            if (!bo.getItemList().isEmpty()) {
+                saveTaskItems(bo.getId(), bo.getItemList());
+            }
+        }
+        return row;
+    }
+
+    /**
+     * 保存巡检项目列表(设置 taskId + 排序号 + 租户)
+     */
+    private void saveTaskItems(Long taskId, java.util.List<RiskInspectionTaskItem> itemList) {
+        for (int i = 0; i < itemList.size(); i++) {
+            RiskInspectionTaskItem item = itemList.get(i);
+            item.setId(null); // 强制新增,避免前端传旧 id 导致 update 失败
+            item.setTaskId(taskId);
+            if (item.getSortOrder() == null) {
+                item.setSortOrder(i);
+            }
+            taskItemMapper.insert(item);
+        }
     }
 
     /**
@@ -315,6 +344,10 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
 
     /**
      * 移动端-校验二维码
+     * <p>
+     * 二维码内容格式:「设备编码 [分隔符] 时间」
+     * 设备编码部分必须等于 task.punchDeviceCode(开启扫码打卡时必填)
+     * 时间部分必须是 yyyy-MM-dd HH:mm:ss 格式,误差 ±24h 内视为有效
      */
     @Override
     public boolean verifyQrCode(Long taskId, String qrCode) {
@@ -322,10 +355,41 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
             return false;
         }
         RiskInspectionTask task = baseMapper.selectById(taskId);
-        if (task == null || StringUtils.isBlank(task.getQrCode())) {
+        if (task == null) {
+            return false;
+        }
+        String content = qrCode.trim();
+        // 解析「设备编码」与「时间」两个部分
+        String[] parts = content.split("[\\s|\\n\\r,;]+", 2);
+        if (parts.length < 2) {
+            // 只有一个部分,按传统方式整体比对
+            return StringUtils.isNotBlank(task.getQrCode())
+                && task.getQrCode().trim().equals(content);
+        }
+        String deviceCode = parts[0].trim();
+        String timeStr = parts[1].trim();
+        // 1. 校验设备编码
+        if (StringUtils.isNotBlank(task.getPunchDeviceCode())
+            && !task.getPunchDeviceCode().trim().equalsIgnoreCase(deviceCode)) {
+            log.warn("扫码设备编码不匹配: taskId={}, expected={}, actual={}",
+                taskId, task.getPunchDeviceCode(), deviceCode);
+            return false;
+        }
+        // 2. 校验时间(误差 ±24 小时)
+        try {
+            java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            Date qrTime = sdf.parse(timeStr);
+            long diffMs = Math.abs(System.currentTimeMillis() - qrTime.getTime());
+            long oneDayMs = 24L * 60 * 60 * 1000;
+            if (diffMs > oneDayMs) {
+                log.warn("二维码时间超出有效期: taskId={}, qrTime={}", taskId, timeStr);
+                return false;
+            }
+        } catch (java.text.ParseException e) {
+            log.warn("二维码时间格式不合法: taskId={}, timeStr={}", taskId, timeStr);
             return false;
         }
-        return task.getQrCode().trim().equals(qrCode.trim());
+        return true;
     }
 
     /**