✨ 管理端认证审核接口,并弃用旧车辆审核入口

审核作用于**一条认证记录**,不是车辆行或司机行。两条路径的差别是本次改造的关键:
- 通过:记录转已通过 + 写审核人/审核时间,并把该对象的生效快照指针**切换**到它
  (切换而非追加,故一个对象至多一条生效快照);
- 驳回:记录转已驳回并留原因,生效快照指针**一个字都不动**。

因此「已认证的车被驳回」不会掉出可用池,只是这次提交没被采纳。

已终态(已通过/已驳回/已作废)的记录拒绝再次审核。理由:若允许,
把指针指着的那条「已通过」改成「已驳回」,生效值就凭空消失了。

审核详情同时返回待审快照与当前生效值两侧,供审核页做字段级 diff
(这正是「看出司机改了什么」的实质要求),并附 certified 标记,
便于前端提示「本次驳回不影响该对象继续使用」。

旧接口 POST /transport-vehicle/audit-certification 置为 @Deprecated 并加告警日志:
它作用于车辆行且原地覆盖,在生效值/待审值分离后语义已不成立。
保留而不删除是为了避免小程序历史版本或其它系统 404;它只写展示缓存,
不再影响可用性判定(可用性只看 effective_cert_record_id)。

修复过程中被测试抓到两个真实问题:
1. 通过路径原先对同一行连发两次 UPDATE(先切指针、再刷展示缓存),
   合并为一次,消除了「后写覆盖先写」的可能;
2. 新增 EffectivePointerUpdateSqlTest,直接断言 MyBatis-Plus 生成的 UPDATE SET 片段:
   指针列必须被「值非 null 才写」的 if 包住——驳回传 null 因此物理上碰不到它。
   把「靠约定」变成「靠语句」,若有人给该字段加 ALWAYS 策略,测试立刻变红。

13 个审核用例 + 全模块 48 个测试通过:mvn -o test -pl blade-service/blade-transport -am
This commit is contained in:
2026-10-02 16:26:20 +08:00
parent 63762b9dd0
commit 5747975152
7 changed files with 859 additions and 1 deletions
@@ -0,0 +1,91 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.pojo.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 认证审核详情视图
* <p>
* 审核是「拿新提交的内容和当前生效的内容比」的过程,所以这里**同时**给出两侧:
* {@code pendingSnapshotJson} 是本次待审的全量内容,
* {@code effectiveSnapshotJson} 是该对象当前生效的全量内容。
* 前端对这两个 JSON 做字段级 diff,即可看出司机这次改了什么。
* <p>
* 两侧的 JSON 结构完全一致(同一套提交结构),因此可以逐键直接比较。
*
* @author Chill
*/
@Data
@Schema(description = "认证审核详情")
public class CertAuditDetailVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "认证记录主键")
private Long id;
@Schema(description = "认证对象类型:driver-司机,vehicle-车辆")
private String bizType;
@Schema(description = "认证对象主键")
private Long bizId;
@Schema(description = "展示键:司机姓名 / 车牌号")
private String bizKey;
@Schema(description = "审核状态:0审核中 1已通过 2已驳回 3已作废")
private Integer status;
@Schema(description = "状态中文文案")
private String statusText;
@Schema(description = "本次提交时间")
private LocalDateTime submitTime;
@Schema(description = "本次提交人")
private Long submitUser;
@Schema(description = "本次提交人姓名")
private String submitUserName;
@Schema(description = "待审快照:本次提交的全量内容")
private String pendingSnapshotJson;
@Schema(description = "生效快照:该对象当前生效的全量内容;从未认证通过时为 null")
private String effectiveSnapshotJson;
@Schema(description = "生效快照记录ID;null 表示该对象从未认证通过")
private Long effectiveRecordId;
/**
* 该对象是否已认证
* <p>
* 审核页据此提示「本次驳回不会影响该对象继续使用」——
* 已认证的对象即便本次被驳回,既得认证依然有效。
*/
@TableField(exist = false)
@Schema(description = "该对象当前是否已认证")
private Boolean certified;
}