From 5dff84255c0fa72a08edda5c4ea2046721b81b84 Mon Sep 17 00:00:00 2001 From: chenjy Date: Wed, 20 May 2026 15:45:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AM-1/AM-2/AM-3=20PRD=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=B7=AE=E5=BC=82=E8=A1=A5=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AM-1 录屏设置:save/update 添加录屏时间范围校验(必须在广告播放时间内) - AM-2 随机录屏:新增 district/screenName 关联查询 + 查询时间范围不超过90天限制 - AM-3 广告监控:新增时间范围/监控人员/大屏名称筛选 + 默认过滤已归档记录 - AM-3 违规判定自动触发 CW-1 取证记录创建 Co-Authored-By: Claude Opus 4.7 --- findings.md | 89 ++++++++++++++++--- progress.md | 51 +++++++++++ .../impl/RecordingConfigServiceImpl.java | 35 ++++++++ .../entity/query/MonitorRecordQuery.java | 20 +++++ .../impl/MonitorRecordServiceImpl.java | 73 ++++++++++++++- .../task/entity/query/RecordingTaskQuery.java | 10 +++ .../impl/RecordingTaskServiceImpl.java | 26 ++++++ 7 files changed, 292 insertions(+), 12 deletions(-) diff --git a/findings.md b/findings.md index a00e58f..37e6d58 100644 --- a/findings.md +++ b/findings.md @@ -159,17 +159,84 @@ #### 缺失接口汇总 -| 类别 | 缺失接口数 | 涉及模块 | -|------|-----------|---------| -| 导入/导出/模板下载 | 3 | BS-1, MR-1 | -| 唯一性校验 | 2 | BS-1, LB-1 | -| 状态变更/废止 | 2 | BS-1, AM-1, LB-1 | -| 历史版本 | 1 | BS-1 | -| 文件下载/播放 | 2 | CW-1 | -| 级联选择(区域/部门/人员) | 3 | CW-4 | -| 线索预览/统计 | 2 | CW-3 | -| 处置反馈 | 1 | CW-4 | -| **合计** | **~16** | | +> 注:下表中标记 ❌ 的接口已在 2026-05-18 的"缺失接口补齐"和"CW-4 完善"中全部实现。此表保留作为历史对照。 + +| 类别 | 原缺失数 | 当前状态 | +|------|---------|---------| +| 导入/导出/模板下载 | 3 | ✅ 已补齐 | +| 唯一性校验 | 2 | ✅ 已补齐 | +| 状态变更/废止 | 2 | ✅ 已补齐 | +| 历史版本 | 1 | ✅ 已补齐 | +| 文件下载/播放 | 2 | ✅ 已补齐 | +| 级联选择 | 3 | ✅ 已补齐 | +| 线索预览/统计 | 2 | ✅ 已补齐 | +| 处置反馈 | 1 | ✅ 已补齐 | +| CW-4 业务闭环 | 6 | ✅ 已补齐(pending-clues/status-update/urge/withdraw/operation-logs) | + +--- + +### PRD vs 后端代码详细差异分析(2026-05-18 更新) + +> 基础 CRUD 和 P0-P3 补齐接口均已实现并通过端到端测试。以下仅列出**仍然存在的差异**。 + +#### 一、风格差异(不影响功能) + +| 项目 | PRD 要求 | 后端实现 | 影响 | +|------|---------|---------|------| +| 路径命名 | 复数名词 `/api/screens` | 单数名词 `/api/screen` | 不影响 | +| HTTP 方法 | RESTful(PUT/DELETE) | 统一 POST | 不影响 | +| 状态枚举值 | 字符串 "RECORDING"/"COMPLETED" | 数字 1/2/3 | 不影响 | +| 分页默认大小 | 20 条/页 | 10 条/页 | 不影响 | +| API 前缀 | 部分 PRD 要求 `/api/cw/` | 无 `/cw/` 前缀 | 不影响 | + +#### 二、缺失的业务逻辑(需补充) + +##### AM-1 录屏设置 +- **唯一性校验**:PRD 要求同一大屏只能有一条配置,后端未在 save/update 中校验 +- **时间范围校验**:PRD 要求录屏时间必须在广告播放时间段内,后端未实现 + +##### AM-2 随机录屏 +- **查询条件缺失**:缺少 `district`(区域)和 `screenName`(大屏名称模糊匹配)筛选条件 +- **时间范围限制**:PRD 要求最大查询范围不超过 90 天,后端未限制 + +##### AM-3 广告画面监控 +- **查询条件缺失**:缺少时间范围(start_time/end_time)、监控人员(monitor_person)筛选 +- **默认状态过滤**:PRD 要求默认只查询未归档记录,后端未实现 +- **并发控制**:PRD 要求同一记录只能由一人监控,后端未实现 +- **自动联动**:PRD 要求判定为违法时自动触发 CW-1 取证流程,后端未实现 + +##### CW-1 固化取证 +- **状态历史独立接口**:PRD 要求 `GET /evidence-records/{id}/status-history`,当前详情接口已含但无独立入口 +- **关联上下文接口**:PRD 要求 `GET /monitor-records/{id}/evidence-context`(来源监控记录关联信息),后端未实现 + +##### CW-2 规则关联 +- **搜索可关联规则**:PRD 要求搜索可关联的监测规则列表,当前只能在 MR-1 的 enabled 接口获取 + +##### CW-3 线索生成 +- **线索生成日志**:PRD 要求 `GET /monitoring-clues/{id}/generation-logs`,后端未实现独立接口 +- **广告主补充**:PRD 要求线索确认时可补充广告主信息,后端 generate 接口未支持 + +#### 三、缺失的通用功能 + +| 功能 | 涉及模块 | 说明 | +|------|---------|------| +| 权限控制 | 全部 | PRD 定义了市局/区局角色数据范围过滤,后端未实现 | +| 关联对象返回 | AM-1/2/3, CW-1 | PRD 要求返回 screen_info/config_info/video_info 嵌套对象,后端返回平铺字段 | +| 状态中文名 | 全部 | PRD 要求返回 `xxx_text` 中文字段(如 task_status_text),后端未实现 | +| 归档标志 | AM-3 | PRD 要求 archive_flag 字段区分已归档/未归档,后端无此字段 | + +#### 四、CW-4 已超出 PRD 的实现 + +以下接口为后端主动扩展,PRD 中未要求但已实现: + +| 接口 | 说明 | +|------|------| +| POST /api/clue-transfer/status-update | 外部系统回调状态流转 | +| POST /api/clue-transfer/urge | 催办 | +| POST /api/clue-transfer/withdraw | 撤回 | +| GET /api/clue-transfer/pending-clues/query | 待转办线索列表 | +| GET /api/clue-transfer/pending-clues/detail | 待转办线索详情 | +| GET /api/clue-transfer/operation-logs | 操作日志独立查询 | ### 框架 API 发现(2026-05-18 编译修复过程中) diff --git a/progress.md b/progress.md index 9f4ec19..a287739 100644 --- a/progress.md +++ b/progress.md @@ -188,6 +188,57 @@ **总通过率**: 40/40 = 100%(所有可用接口均正常响应) +### 2026-05-18 — CW-4 线索转办模块完善 + +**新增 6 个接口**(7 个新文件 + 3 个修改文件,+435 行): + +| # | 接口 | 说明 | +|---|------|------| +| 1 | POST /api/clue-transfer/pending-clues/query | 待转办线索列表(clue_status=1) | +| 2 | GET /api/clue-transfer/pending-clues/detail | 待转办线索详情 | +| 3 | GET /api/clue-transfer/operation-logs | 转办操作日志 | +| 4 | POST /api/clue-transfer/status-update | 状态流转(transferred→processing→completed/failed) | +| 5 | POST /api/clue-transfer/urge | 催办 | +| 6 | POST /api/clue-transfer/withdraw | 撤回(线索回退到待转办) | + +**新增文件**: +- PendingClueQuery.java, PendingClueVO.java, PendingClueDetailVO.java +- OperationLogVO.java +- TransferStatusUpdateReq.java, TransferUrgeReq.java, TransferWithdrawReq.java + +**端到端测试结果**: +- 完整流转 transferred→processing→completed ✅ +- 双表状态同步(线索 1→2→3→4)✅ +- 操作日志 4 条完整记录 ✅ +- 催办记录日志 ✅ +- 撤回后线索回退到 1 ✅ +- 非法状态流转拦截 ✅ + +**提交**: `687eb9a` feat: CW-4 线索转办模块完善 — 新增6个接口覆盖完整业务闭环 + +### 2026-05-18 — PRD 业务逻辑差异补齐 + +**操作**: 按优先级分 4 批补齐 AM-1/AM-2/AM-3 的 PRD 业务逻辑差异 + +**批次 1: AM-3 广告画面监控** (修改 2 文件): +- MonitorRecordQuery 添加 startTime/endTime/monitorPerson/screenName 字段 +- MonitorRecordServiceImpl.queryList 补充时间范围/监控人员/大屏名称筛选条件 +- MonitorRecordServiceImpl.judgeMonitor 判定为违规(4)时自动创建取证记录 + +**批次 2: AM-2 随机录屏** (修改 2 文件): +- RecordingTaskQuery 添加 district/screenName 字段 +- RecordingTaskServiceImpl 通过 ScreenMapper 两步关联查询 + +**批次 3: AM-1 录屏设置** (修改 1 文件): +- RecordingConfigServiceImpl save/update 添加录屏时间范围校验 +- 校验 recordStartTime/recordEndTime 必须在大屏广告播放时间范围内 + +**批次 4: 低优先级通用改进** (修改 2 文件): +- AM-3 默认过滤已判定记录(monitorStatus < 3) +- AM-2 查询时间范围超过 90 天时拦截 + +**编译结果**: 全部通过 ✅ + --- ## 阶段执行状态 diff --git a/src/main/java/com/chinaweal/youfool/prj/modules/monitor/config/service/impl/RecordingConfigServiceImpl.java b/src/main/java/com/chinaweal/youfool/prj/modules/monitor/config/service/impl/RecordingConfigServiceImpl.java index 06051f5..6ba7c50 100644 --- a/src/main/java/com/chinaweal/youfool/prj/modules/monitor/config/service/impl/RecordingConfigServiceImpl.java +++ b/src/main/java/com/chinaweal/youfool/prj/modules/monitor/config/service/impl/RecordingConfigServiceImpl.java @@ -90,6 +90,10 @@ public class RecordingConfigServiceImpl extends ServiceImpl checkWrapper = new LambdaQueryWrapper<>(); checkWrapper.eq(RecordingConfigEntity::getScreenId, req.getScreenId()); @@ -137,6 +141,18 @@ public class RecordingConfigServiceImpl extends ServiceImpl 0) { + throw new IllegalArgumentException("录屏结束时间不能晚于广告播放结束时间(" + adEnd + ")"); + } + } } diff --git a/src/main/java/com/chinaweal/youfool/prj/modules/monitor/record/entity/query/MonitorRecordQuery.java b/src/main/java/com/chinaweal/youfool/prj/modules/monitor/record/entity/query/MonitorRecordQuery.java index bfe951f..66d3a41 100644 --- a/src/main/java/com/chinaweal/youfool/prj/modules/monitor/record/entity/query/MonitorRecordQuery.java +++ b/src/main/java/com/chinaweal/youfool/prj/modules/monitor/record/entity/query/MonitorRecordQuery.java @@ -37,4 +37,24 @@ public class MonitorRecordQuery { * 行政区划 */ private String district; + + /** + * 监控时间范围起始(yyyy-MM-dd HH:mm:ss) + */ + private String startTime; + + /** + * 监控时间范围结束(yyyy-MM-dd HH:mm:ss) + */ + private String endTime; + + /** + * 监控人员 + */ + private String monitorPerson; + + /** + * 大屏名称(模糊查询) + */ + private String screenName; } diff --git a/src/main/java/com/chinaweal/youfool/prj/modules/monitor/record/service/impl/MonitorRecordServiceImpl.java b/src/main/java/com/chinaweal/youfool/prj/modules/monitor/record/service/impl/MonitorRecordServiceImpl.java index cb7965d..b2910f1 100644 --- a/src/main/java/com/chinaweal/youfool/prj/modules/monitor/record/service/impl/MonitorRecordServiceImpl.java +++ b/src/main/java/com/chinaweal/youfool/prj/modules/monitor/record/service/impl/MonitorRecordServiceImpl.java @@ -7,14 +7,20 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.chinaweal.youfool.framework.springboot.common.util.AssertUtils; import com.chinaweal.youfool.framework.springboot.common.util.StringUtils; import com.chinaweal.youfool.framework.springboot.rest.RestResult; +import com.chinaweal.youfool.prj.modules.evidence.record.entity.EvidenceRecordEntity; +import com.chinaweal.youfool.prj.modules.evidence.record.mapper.EvidenceRecordMapper; import com.chinaweal.youfool.prj.modules.monitor.record.entity.MonitorRecordEntity; import com.chinaweal.youfool.prj.modules.monitor.record.entity.query.MonitorRecordQuery; import com.chinaweal.youfool.prj.modules.monitor.record.entity.req.MonitorJudgeReq; import com.chinaweal.youfool.prj.modules.monitor.record.mapper.MonitorRecordMapper; import com.chinaweal.youfool.prj.modules.monitor.record.service.IMonitorRecordService; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Date; /** @@ -25,8 +31,11 @@ import java.util.Date; */ @Slf4j @Service +@AllArgsConstructor public class MonitorRecordServiceImpl extends ServiceImpl implements IMonitorRecordService { + private final EvidenceRecordMapper evidenceRecordMapper; + @Override public RestResult> queryList(MonitorRecordQuery query) { Page page = new Page<>(query.getPageNum(), query.getPageSize()); @@ -35,14 +44,40 @@ public class MonitorRecordServiceImpl extends ServiceImpl entityPage = this.page(page, wrapper); return RestResult.ok(entityPage); @@ -101,6 +136,42 @@ public class MonitorRecordServiceImpl extends ServiceImpl implements IRecordingTaskService { private final AlertNotificationMapper alertNotificationMapper; + private final ScreenMapper screenMapper; @Override public RestResult> queryList(RecordingTaskQuery query) { @@ -50,12 +53,35 @@ public class RecordingTaskServiceImpl extends ServiceImpl screenWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.isNotBlank(query.getDistrict())) { + screenWrapper.eq(ScreenEntity::getDistrict, query.getDistrict()); + } + if (StringUtils.isNotBlank(query.getScreenName())) { + screenWrapper.like(ScreenEntity::getScreenName, query.getScreenName()); + } + List screens = screenMapper.selectList(screenWrapper); + List screenIds = screens.stream() + .map(ScreenEntity::getId).collect(Collectors.toList()); + if (screenIds.isEmpty()) { + // 无匹配大屏,返回空结果 + return RestResult.ok(new Page<>(query.getPageNum(), query.getPageSize(), 0)); + } + wrapper.in(RecordingTaskEntity::getScreenId, screenIds); + } // 开始时间范围筛选 if (StringUtils.isNotBlank(query.getStartTimeBegin()) && StringUtils.isNotBlank(query.getStartTimeEnd())) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { Date beginDate = sdf.parse(query.getStartTimeBegin()); Date endDate = sdf.parse(query.getStartTimeEnd()); + // 限制查询范围不超过90天 + long diffDays = (endDate.getTime() - beginDate.getTime()) / (1000 * 60 * 60 * 24); + if (diffDays > 90) { + throw new IllegalArgumentException("查询时间范围不能超过90天"); + } wrapper.ge(RecordingTaskEntity::getActualStartTime, beginDate); wrapper.le(RecordingTaskEntity::getActualStartTime, endDate); } catch (ParseException e) {