# 核销记录对外分页 + 撤销核销迁移管理端 > 状态:**计划已定,待执行**(2026-06-26 暂存,用户优先处理别的问题) > 关联需求:券码核验记录增加对外分页接口 + web 端撤销核销迁移到管理端 ## 已确认口径(AskUserQuestion 答复) 1. 「订单编号(discount_member)」= **`discount_member.goodsorder_id`**(核销时自动建的 goodsorder 订单编码) 2. 「团购商品名称」= `douyin_product.product_name`;「抖音券名称」= **`discount_member.name`**(本地套餐名) 3. 撤销核销迁移管理端后 **取消「核销后1小时内」限制**,运营随时可撤销 4. web 端原 `/web/douyin/cancel` **删除**,纯迁移到管理端 5. 「兑换人」= **`member.name`**(会员姓名);手机号脱敏规则 `138****1234`(前3后4,<7位原样) 6. 撤销核销**同步作废**本地套餐卡(`discount_member.status`=1 作废)+ 记 `discount_log` 7. 查询条件增加**撤销状态**(`cancel_status` 0未撤销/1已撤销)筛选 8. 管理端撤销**落 `douyin_verify_log`**(operateType=CANCEL),沿用小程序核销审计链路 ## 字段映射 ### 查询条件(3 项) | 条件 | 字段 | |---|---| | 抖音券码 | `verify_record.origin_code`(eq,券码唯一) | | 验券状态 | `verify_record.verify_status`(eq,0成功 1失败) | | 撤销状态 | `verify_record.cancel_status`(eq,0未撤销 1已撤销) | ### 返回 VO(`DouyinVerifyRecordPageVO`) | 列表字段 | 来源 | 关联 | |---|---|---| | 订单编号 | `discount_member.goodsorder_id` | LEFT JOIN discount_member ON `discount_member_id` | | 会员openid | `member.openid` | LEFT JOIN member ON `discount_member.member_id` | | 会员手机号(脱敏) | `member.phone` → `138****1234` | 同上 join,service 遍历脱敏 | | 团购商品名称 | `douyin_product.product_name` | LEFT JOIN douyin_product ON `verify.product_id = product.product_id`(非主键) | | 抖音券名称 | `discount_member.name` | discount_member join | | 抖音券类型(类目) | `douyin_product.category` | douyin_product join | | 验券时间 | `verify_record.verify_time` | 主表 | | 兑换人 | `member.name` | 核销操作人=购买会员本人,复用 member join 取姓名 | | 状态 | `verify_status`+`cancel_status` 综合文案 `statusName`(已兑换/已撤销/核销失败)+ 原值 | 主表 | > 兑换人:`verify_user_id` 与 `discount_member.member_id` 同为核销会员 id(核销时 operator 即套餐归属人),复用同一次 member join 取姓名。 > 脱敏规则:`138****1234`(前3后4,中间4位*),<7 位原样返回。 ## MPJ 关联设计 主表 `douyin_verify_record`(别名 `t`) - LEFT JOIN `discount_member` dm ON `t.discount_member_id = dm.id` - LEFT JOIN `member` m ON `dm.member_id = m.id` - LEFT JOIN `douyin_product` p ON `t.product_id = p.product_id`(**非主键字段**,MPJ leftJoin 支持任意字段) selectAs:dm.goodsorder_id→orderCode、m.openid→memberOpenid、m.phone→memberPhone(脱敏)、p.product_name→productName、dm.name→couponName、p.category→category、m.name→exchangerName;selectAll(DouyinVerifyRecord)带 id/verify_time/verify_status/cancel_status/origin_code 等。 ## 文件改动清单 ### 新增 1. `services/src/main/java/com/doumee/dao/business/vo/DouyinVerifyRecordPageVO.java` — 分页返回 VO(含 originCode/verifyStatus 查询入参 + 渲染字段 + statusName) 2. `platform/src/main/java/com/doumee/api/business/DouyinVerifyController.java` — `@RequestMapping("/business/douyinVerify")` - `POST /page` → findManagePage,`@RequiresPermissions("business:douyinVerify:query")` - `POST /cancel` → cancel,`@RequiresPermissions("business:douyinVerify:cancel")`,`@PreventRepeat` ### 修改 3. `services/src/main/java/com/doumee/dao/business/DouyinVerifyRecordMapper.java` — `BaseMapper` → `MPJJoinMapper`(沿用 DouyinProductMapper 模式,支持 selectJoinPage) 4. `services/src/main/java/com/doumee/service/business/DouyinVerifyService.java` — 新增 `findManagePage(PageWrap) -> PageData`(**不动**现有 `findPage`,web `/web/douyin/page` 仍用) 5. `services/src/main/java/com/doumee/service/business/impl/DouyinVerifyServiceImpl.java` - 新增 `findManagePage`:MPJ 三表 leftJoin + selectAs 映射 + 手机号脱敏 + statusName 文案 - **删除 cancel 的「核销后1小时内」窗口检查**(现 line 374-378) - cancel 成功后**作废套餐卡**:经 `rec.discountMemberId` 把 `discount_member.status` 置 1 + 记 `discount_log`(参照 backGoodsorder 退卡写法) - cancel 加 `@Transactional`(抖音撤销/作废套餐/记录更新原子);撤销日志由 controller 落 douyin_verify_log 6. `web/src/main/java/com/doumee/api/web/DouyinApi.java` — 删 `cancel` 方法 + 仅其用的 `OPERATE_CANCEL` 常量 + `fillByRecord` 的 cancel 分支 / `CANCEL_DONE`;清理 `DouyinCancelParam` import 7. `services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelParam.java` — 注释由「web 端小程序」改为「管理端」 8. `db/Shiro.sql` — 幂等登记 `business:douyinVerify:query`、`business:douyinVerify:cancel` + 授权 role 1 ## 取舍 / 不动项 - **不动** `findPage`(web 端 `/web/douyin/page` 保留)、verify/prepare 链路、商品同步/绑定、`openDiscountForVerify`。 - **撤销行为(增强)**:抖音撤销成功后 → 作废本地套餐卡(`discount_member.status`=1)+ 记 `discount_log` + 更新 verify_record(cancel_status/time/user)。 - **撤销日志**:管理端撤销落 `douyin_verify_log`(operateType=CANCEL,沿用 web 端那套);cancel service 加 `@Transactional`,抖音撤销/作废套餐/记录更新原子。 ## 验证 - `mvn -pl platform -am clean compile -o`(services + web + platform 全量) - 执行 `db/Shiro.sql`(幂等) - 手测 `/doc.html`:`POST /business/douyinVerify/page`(券码/状态筛选 + 各字段回填 + 手机号脱敏)、`POST /business/douyinVerify/cancel`(超时也能撤销) ## 关键现状备忘(执行时参考) - `Member.phone`(model line 96)、`Member.openid`(line 66);`discount_member.code`(票号)、`goodsorder_id`;`member.id` 关联 `discount_member.member_id` - platform 取登录用户:`BaseController.getLoginUser().getId()`(Shiro `LoginUserInfo`) - `DouyinVerifyRecordMapper` 现为 `BaseMapper`(待改 MPJJoinMapper) - cancel 入参 `DouyinCancelParam{id}`;cancel 1 小时窗口在 `DouyinVerifyServiceImpl` line 374-378 - `douyin_product.product_id` 为业务唯一键(upsertProduct 按 product_id selectOne 无 limit,视为唯一),LEFT JOIN 不会膨胀 - `DiscountMemberServiceImpl` 是 MPJ leftJoin + selectAs + 子查询标量的黄金先例 - 撤销作废套餐卡写法参照 `GoodsorderServiceImpl.backGoodsorder`(line 1020-1042):按 `rec.discountMemberId` 查 discount_member,仅 status=0 才作废(update set status=1 where id)+ 记 DiscountLog(type=1, editInfo="撤销核销作废", creator=operator, discountMemberId, goodsorderId=dm.goodsorderId);已作废跳过(幂等)