✨ 新增认证记录不可变快照数据模型与迁移脚本
按 ADR-0001/0002 建立司机与车辆共用的认证记录表 blade_transport_cert_record (biz_type 区分、载荷存 JSON、检索字段提升为真实列),并为司机/车辆表补生效快照指针。 不变式结构性保证: - I1 同对象至多一条待审:生成列 pending_biz_id + 唯一索引,数据库层拦截; 生成列同时排除 is_deleted,避免逻辑删除的待审记录永久占用唯一键。 - I3 快照不可变:snapshot_json 等 5 列标 FieldStrategy.NEVER,永不进入 UPDATE。 - status 承载审核状态(0审核中/1已通过/2已驳回/3已作废),新增时必须显式设置: 框架 resolveEntity 会把 null 的 status 静默填成 1(即「已通过」), 且继承来的 changeStatus 可直接写 status,两条通道均已封堵。 其他:司机表补身份证有效期三列并订正排序规则为 utf8mb4_general_ci (原先为 0900_ai_ci,与车辆表 JOIN 已发生过 Illegal mix of collations); 生效快照指针加 @JsonProperty(READ_ONLY),防止以 /submit 请求体自行「认证」自己; 同步基线建表脚本与认证审核菜单/权限码。本票不含任何业务接口。
This commit is contained in:
+44
@@ -28,6 +28,9 @@ package org.springblade.transport.pojo.entity;
|
|||||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -111,6 +114,21 @@ public class Driver extends TenantEntity {
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "身份证反面照")
|
@Schema(description = "身份证反面照")
|
||||||
private String idCardBack;
|
private String idCardBack;
|
||||||
|
/**
|
||||||
|
* 身份证有效期起
|
||||||
|
*/
|
||||||
|
@Schema(description = "身份证有效期起")
|
||||||
|
private LocalDate idCardValidStart;
|
||||||
|
/**
|
||||||
|
* 身份证有效期止
|
||||||
|
*/
|
||||||
|
@Schema(description = "身份证有效期止")
|
||||||
|
private LocalDate idCardValidEnd;
|
||||||
|
/**
|
||||||
|
* 身份证长期有效
|
||||||
|
*/
|
||||||
|
@Schema(description = "身份证长期有效")
|
||||||
|
private Integer idCardLongTerm;
|
||||||
/**
|
/**
|
||||||
* 大头照
|
* 大头照
|
||||||
*/
|
*/
|
||||||
@@ -221,5 +239,31 @@ public class Driver extends TenantEntity {
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "备注")
|
@Schema(description = "备注")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
/**
|
||||||
|
* 最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null 表示从未提交
|
||||||
|
* <p>
|
||||||
|
* 仅为列表展示而冗余,<b>不是</b>可用性判据;「是否已认证」一律读生效快照指针。
|
||||||
|
*/
|
||||||
|
@Schema(description = "最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null为从未提交")
|
||||||
|
private Integer certificationStatus;
|
||||||
|
/**
|
||||||
|
* 最新一次驳回原因展示缓存
|
||||||
|
*/
|
||||||
|
@Schema(description = "最新一次驳回原因展示缓存")
|
||||||
|
private String certificationRejectReason;
|
||||||
|
/**
|
||||||
|
* 生效快照指针:指向 blade_transport_cert_record.id
|
||||||
|
* <p>
|
||||||
|
* 「该司机是否已认证」的<b>唯一</b>判据。为 null 表示从未认证通过;
|
||||||
|
* 一经设置<b>永不回退</b>——提交、驳回、作废都不得修改它(见 ADR-0002)。
|
||||||
|
* <p>
|
||||||
|
* ⚠️ {@code READ_ONLY}:本字段<b>只能由审核链路</b>(03)写入。
|
||||||
|
* 司机档案的 {@code /submit} 直接以本实体接收请求体,若允许反序列化,
|
||||||
|
* 调用方就能自行填一个记录 ID 把自己「认证」掉,绕过审核与 D10 的可用性门槛。
|
||||||
|
*/
|
||||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "生效快照指针")
|
||||||
|
private Long effectiveCertRecordId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
/**
|
||||||
|
* 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.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录实体类
|
||||||
|
* <p>
|
||||||
|
* 认证对象(司机 / 车辆)<b>每一次提交</b>所产生的、<b>不可变</b>的快照。
|
||||||
|
* 记录一经创建,其 {@code snapshotJson} 与提交元信息永不修改;被同对象的后续提交取代时
|
||||||
|
* 标记为「已作废」,而<b>不是</b>删除。判定某个对象「是否已认证」只看它持有的生效快照指针,
|
||||||
|
* 与记录表里的任何一条记录的状态都无关。
|
||||||
|
* <p>
|
||||||
|
* 设计依据:{@code docs/adr/0001-certification-record-immutable-snapshot.md},
|
||||||
|
* 术语见根工作区 {@code GLOSSARY.md}。
|
||||||
|
* <p>
|
||||||
|
* ⚠️ <b>{@code status} 承载的是「审核状态」,不是 BladeX 的「启用 / 停用」。</b>
|
||||||
|
* 该字段继承自 {@link TenantEntity},带来两条必须堵住的框架通道:
|
||||||
|
* 一是 {@code BaseServiceImpl#resolveEntity} 在插入时会把 {@code null} 的 {@code status}
|
||||||
|
* <b>静默填成 {@code 1}</b>(恰好是 {@link #STATUS_APPROVED});
|
||||||
|
* 二是继承来的 {@code changeStatus(ids, 1)} 会直接写 {@code status},等于批量通过审核。
|
||||||
|
* 二者都已在 {@code TransportCertRecordServiceImpl} 中封堵(前者校验抛错、后者覆写抛错),
|
||||||
|
* 审核一律走 03 的审核接口。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("blade_transport_cert_record")
|
||||||
|
@Schema(description = "认证记录")
|
||||||
|
public class TransportCertRecord extends TenantEntity {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证对象类型:司机
|
||||||
|
*/
|
||||||
|
public static final String BIZ_TYPE_DRIVER = "driver";
|
||||||
|
/**
|
||||||
|
* 认证对象类型:车辆
|
||||||
|
*/
|
||||||
|
public static final String BIZ_TYPE_VEHICLE = "vehicle";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核状态:审核中(即该对象的「待审快照」)
|
||||||
|
*/
|
||||||
|
public static final Integer STATUS_PENDING = 0;
|
||||||
|
/**
|
||||||
|
* 审核状态:已通过(该对象最新一条已通过者即其「生效快照」)
|
||||||
|
*/
|
||||||
|
public static final Integer STATUS_APPROVED = 1;
|
||||||
|
/**
|
||||||
|
* 审核状态:已驳回(审核的否定结论,需带驳回原因)
|
||||||
|
*/
|
||||||
|
public static final Integer STATUS_REJECTED = 2;
|
||||||
|
/**
|
||||||
|
* 审核状态:已作废(被同对象的再次提交取代,非驳回)
|
||||||
|
*/
|
||||||
|
public static final Integer STATUS_SUPERSEDED = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证对象类型:driver-司机,vehicle-车辆
|
||||||
|
* <p>
|
||||||
|
* {@code NEVER}:记录一经创建,认证对象的身份不可改(详见类注释的不变式 I3)。
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@Schema(description = "认证对象类型:driver-司机,vehicle-车辆")
|
||||||
|
private String bizType;
|
||||||
|
/**
|
||||||
|
* 认证对象主键:司机ID 或 车辆ID(车辆按具体车辆,不是司机)
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "认证对象主键")
|
||||||
|
private Long bizId;
|
||||||
|
/**
|
||||||
|
* 展示键:司机姓名 / 车牌号,供审核列表直接展示
|
||||||
|
*/
|
||||||
|
@Schema(description = "展示键:司机姓名 / 车牌号")
|
||||||
|
private String bizKey;
|
||||||
|
/**
|
||||||
|
* 该次提交的全量认证内容(JSON),一经创建不可修改
|
||||||
|
* <p>
|
||||||
|
* {@code NEVER}:这是不变式 <b>I3</b> 的结构性保证——本字段<b>永不进入 UPDATE 语句</b>,
|
||||||
|
* 因此不存在「某条链路忘了保护快照」的可能。改内容只能靠<b>新提交一条记录</b>。
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@Schema(description = "该次提交的全量认证内容JSON")
|
||||||
|
private String snapshotJson;
|
||||||
|
/**
|
||||||
|
* 驳回原因,仅当审核状态为已驳回时有值
|
||||||
|
*/
|
||||||
|
@Schema(description = "驳回原因")
|
||||||
|
private String rejectReason;
|
||||||
|
/**
|
||||||
|
* 提交时间
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@Schema(description = "提交时间")
|
||||||
|
private LocalDateTime submitTime;
|
||||||
|
/**
|
||||||
|
* 提交人
|
||||||
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "提交人")
|
||||||
|
private Long submitUser;
|
||||||
|
/**
|
||||||
|
* 审核时间
|
||||||
|
*/
|
||||||
|
@Schema(description = "审核时间")
|
||||||
|
private LocalDateTime auditTime;
|
||||||
|
/**
|
||||||
|
* 审核人
|
||||||
|
*/
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "审核人")
|
||||||
|
private Long auditUser;
|
||||||
|
|
||||||
|
}
|
||||||
+19
@@ -26,6 +26,9 @@
|
|||||||
package org.springblade.transport.pojo.entity;
|
package org.springblade.transport.pojo.entity;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -224,5 +227,21 @@ public class TransportVehicle extends TenantEntity {
|
|||||||
/** 认证驳回原因 */
|
/** 认证驳回原因 */
|
||||||
@Schema(description = "认证驳回原因")
|
@Schema(description = "认证驳回原因")
|
||||||
private String certificationRejectReason;
|
private String certificationRejectReason;
|
||||||
|
/**
|
||||||
|
* 生效快照指针:指向 blade_transport_cert_record.id
|
||||||
|
* <p>
|
||||||
|
* 「该车辆是否已认证」的<b>唯一</b>判据。为 null 表示从未认证通过;
|
||||||
|
* 一经设置<b>永不回退</b>——重新提交、待审、被驳回都不得修改它(见 ADR-0002)。
|
||||||
|
* 改造后 {@link #certificationStatus} 降级为「最新提交状态」的展示缓存。
|
||||||
|
* <p>
|
||||||
|
* ⚠️ {@code READ_ONLY}:本字段<b>只能由审核链路</b>(03)写入。
|
||||||
|
* {@code POST /transport-vehicle/submit} 直接以本实体接收请求体,
|
||||||
|
* 若允许反序列化,调用方就能自行填一个记录 ID 把自己「认证」掉,
|
||||||
|
* 从而完全绕过审核与 D10 的可用性门槛。
|
||||||
|
*/
|
||||||
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
@Schema(description = "生效快照指针")
|
||||||
|
private Long effectiveCertRecordId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* 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 lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录视图实体类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "认证记录")
|
||||||
|
public class TransportCertRecordVO extends TransportCertRecord {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "创建人姓名")
|
||||||
|
private String createUserName;
|
||||||
|
/**
|
||||||
|
* 更新人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "更新人姓名")
|
||||||
|
private String updateUserName;
|
||||||
|
/**
|
||||||
|
* 提交人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "提交人姓名")
|
||||||
|
private String submitUserName;
|
||||||
|
/**
|
||||||
|
* 审核人姓名
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "审核人姓名")
|
||||||
|
private String auditUserName;
|
||||||
|
|
||||||
|
}
|
||||||
+13
-1
@@ -46,6 +46,12 @@
|
|||||||
<result column="emergency_contact_name" property="emergencyContactName"/>
|
<result column="emergency_contact_name" property="emergencyContactName"/>
|
||||||
<result column="emergency_contact_mobile" property="emergencyContactMobile"/>
|
<result column="emergency_contact_mobile" property="emergencyContactMobile"/>
|
||||||
<result column="remark" property="remark"/>
|
<result column="remark" property="remark"/>
|
||||||
|
<result column="id_card_valid_start" property="idCardValidStart"/>
|
||||||
|
<result column="id_card_valid_end" property="idCardValidEnd"/>
|
||||||
|
<result column="id_card_long_term" property="idCardLongTerm"/>
|
||||||
|
<result column="certification_status" property="certificationStatus"/>
|
||||||
|
<result column="certification_reject_reason" property="certificationRejectReason"/>
|
||||||
|
<result column="effective_cert_record_id" property="effectiveCertRecordId"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="BaseColumn">
|
<sql id="BaseColumn">
|
||||||
@@ -70,6 +76,9 @@
|
|||||||
posts,
|
posts,
|
||||||
id_card_front,
|
id_card_front,
|
||||||
id_card_back,
|
id_card_back,
|
||||||
|
id_card_valid_start,
|
||||||
|
id_card_valid_end,
|
||||||
|
id_card_long_term,
|
||||||
head_photo,
|
head_photo,
|
||||||
driving_type,
|
driving_type,
|
||||||
driving_license_no,
|
driving_license_no,
|
||||||
@@ -91,7 +100,10 @@
|
|||||||
organization_name,
|
organization_name,
|
||||||
emergency_contact_name,
|
emergency_contact_name,
|
||||||
emergency_contact_mobile,
|
emergency_contact_mobile,
|
||||||
remark
|
remark,
|
||||||
|
certification_status,
|
||||||
|
certification_reject_reason,
|
||||||
|
effective_cert_record_id
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectByIdCardNoIncludingDeleted" resultType="org.springblade.transport.pojo.entity.Driver">
|
<select id="selectByIdCardNoIncludingDeleted" resultType="org.springblade.transport.pojo.entity.Driver">
|
||||||
|
|||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 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.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录 Mapper 接口
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
public interface TransportCertRecordMapper extends BaseMapper<TransportCertRecord> {
|
||||||
|
|
||||||
|
}
|
||||||
+2
@@ -48,6 +48,7 @@
|
|||||||
<result column="remark" property="remark"/>
|
<result column="remark" property="remark"/>
|
||||||
<result column="certification_status" property="certificationStatus"/>
|
<result column="certification_status" property="certificationStatus"/>
|
||||||
<result column="certification_reject_reason" property="certificationRejectReason"/>
|
<result column="certification_reject_reason" property="certificationRejectReason"/>
|
||||||
|
<result column="effective_cert_record_id" property="effectiveCertRecordId"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="BaseColumn">
|
<sql id="BaseColumn">
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
remark
|
remark
|
||||||
, certification_status
|
, certification_status
|
||||||
, certification_reject_reason
|
, certification_reject_reason
|
||||||
|
, effective_cert_record_id
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<sql id="ExpiryWithin30Condition">
|
<sql id="ExpiryWithin30Condition">
|
||||||
|
|||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* 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.service;
|
||||||
|
|
||||||
|
import org.springblade.core.mp.base.BaseService;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录 服务类
|
||||||
|
* <p>
|
||||||
|
* 只承载认证记录本身的读写与<b>数据模型不变式</b>的守护;
|
||||||
|
* 提交(02/05)、审核(03)、可用性门槛(04)等业务链路在各自的票中实现。
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
public interface ITransportCertRecordService extends BaseService<TransportCertRecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 某认证对象的认证记录列表,按提交时间倒序
|
||||||
|
*
|
||||||
|
* @param bizType 认证对象类型:driver-司机,vehicle-车辆
|
||||||
|
* @param bizId 认证对象主键
|
||||||
|
* @return 认证记录列表
|
||||||
|
*/
|
||||||
|
List<TransportCertRecordVO> selectRecordList(String bizType, Long bizId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取某认证对象当前的待审快照(至多一条)
|
||||||
|
*
|
||||||
|
* @param bizType 认证对象类型:driver-司机,vehicle-车辆
|
||||||
|
* @param bizId 认证对象主键
|
||||||
|
* @return 待审快照,无则返回 null
|
||||||
|
*/
|
||||||
|
TransportCertRecord selectPendingRecord(String bizType, Long bizId);
|
||||||
|
|
||||||
|
}
|
||||||
+188
@@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.transport.mapper.TransportCertRecordMapper;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||||
|
import org.springblade.transport.service.ITransportCertRecordService;
|
||||||
|
import org.springblade.transport.wrapper.TransportCertRecordWrapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录 服务实现类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class TransportCertRecordServiceImpl extends BaseServiceImpl<TransportCertRecordMapper, TransportCertRecord> implements ITransportCertRecordService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合法的审核状态取值
|
||||||
|
*/
|
||||||
|
private static final Set<Integer> VALID_STATUS = Set.of(
|
||||||
|
TransportCertRecord.STATUS_PENDING,
|
||||||
|
TransportCertRecord.STATUS_APPROVED,
|
||||||
|
TransportCertRecord.STATUS_REJECTED,
|
||||||
|
TransportCertRecord.STATUS_SUPERSEDED
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean save(TransportCertRecord transportCertRecord) {
|
||||||
|
validateRecord(transportCertRecord);
|
||||||
|
return super.save(transportCertRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean saveBatch(Collection<TransportCertRecord> entityList, int batchSize) {
|
||||||
|
entityList.forEach(this::validateRecord);
|
||||||
|
return super.saveBatch(entityList, batchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean saveOrUpdateBatch(Collection<TransportCertRecord> entityList, int batchSize) {
|
||||||
|
entityList.forEach(this::validateRecord);
|
||||||
|
return super.saveOrUpdateBatch(entityList, batchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用继承自 {@code BaseService} 的「修改状态」
|
||||||
|
* <p>
|
||||||
|
* ⚠️ 本表的 {@code status} 是<b>审核状态</b>,不是「启用 / 停用」。
|
||||||
|
* 而 {@code BaseServiceImpl#changeStatus} 会绕过 {@link #validateRecord} 直接写 status,
|
||||||
|
* 调用 {@code changeStatus(ids, 1)} 即等于<b>批量通过审核</b>——
|
||||||
|
* 审核人、审核时间、驳回原因全部缺失,既破坏 I3 的留痕,也会凭空造出生效快照。
|
||||||
|
* 审核必须走 03 的审核接口(作用于单条记录、带留痕)。
|
||||||
|
*
|
||||||
|
* @param ids 主键集合
|
||||||
|
* @param status 状态
|
||||||
|
* @return 永不返回,直接抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean changeStatus(List<Long> ids, Integer status) {
|
||||||
|
throw new ServiceException("认证记录不支持直接修改状态,请使用审核接口");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TransportCertRecordVO> selectRecordList(String bizType, Long bizId) {
|
||||||
|
return list(recordQuery(bizType, bizId)).stream()
|
||||||
|
.map(TransportCertRecordWrapper.build()::entityVO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportCertRecord selectPendingRecord(String bizType, Long bizId) {
|
||||||
|
validateBizTarget(bizType, bizId);
|
||||||
|
return getOne(Wrappers.<TransportCertRecord>lambdaQuery()
|
||||||
|
.eq(TransportCertRecord::getBizType, bizType)
|
||||||
|
.eq(TransportCertRecord::getBizId, bizId)
|
||||||
|
.eq(TransportCertRecord::getStatus, TransportCertRecord.STATUS_PENDING)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证对象检索条件:按提交时间倒序
|
||||||
|
* <p>
|
||||||
|
* 排序用 {@code submit_time} 而非框架默认的 {@code create_time},是刻意为之:
|
||||||
|
* 「认证记录」的业务时间轴是<b>提交</b>,ticket 02 明确要求
|
||||||
|
* 「查询某辆车的认证记录列表:按提交时间倒序」。
|
||||||
|
*
|
||||||
|
* @param bizType 认证对象类型
|
||||||
|
* @param bizId 认证对象主键
|
||||||
|
* @return 检索条件
|
||||||
|
*/
|
||||||
|
private LambdaQueryWrapper<TransportCertRecord> recordQuery(String bizType, Long bizId) {
|
||||||
|
validateBizTarget(bizType, bizId);
|
||||||
|
return Wrappers.<TransportCertRecord>lambdaQuery()
|
||||||
|
.eq(TransportCertRecord::getBizType, bizType)
|
||||||
|
.eq(TransportCertRecord::getBizId, bizId)
|
||||||
|
.orderByDesc(TransportCertRecord::getSubmitTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证对象标识校验
|
||||||
|
*
|
||||||
|
* @param bizType 认证对象类型
|
||||||
|
* @param bizId 认证对象主键
|
||||||
|
*/
|
||||||
|
private void validateBizTarget(String bizType, Long bizId) {
|
||||||
|
if (Func.isEmpty(bizType) || Func.isEmpty(bizId)) {
|
||||||
|
throw new ServiceException("认证对象类型与主键不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录落库前的强制校验
|
||||||
|
* <p>
|
||||||
|
* ⚠️ 这里防的是框架的一个<b>静默</b>行为:{@code BaseServiceImpl#resolveEntity} 在插入时
|
||||||
|
* 若 {@code status} 为 {@code null},会填充 {@code BladeConstant.DB_STATUS_NORMAL}(值 1),
|
||||||
|
* 而 1 在本表恰好是 {@link TransportCertRecord#STATUS_APPROVED}。
|
||||||
|
* 那会让一条「忘了设状态」的提交凭空变成「已通过」,进而可能被当成生效快照。
|
||||||
|
* 故此处对空状态与非法取值直接拒绝,不依赖调用方自觉。
|
||||||
|
*
|
||||||
|
* @param transportCertRecord 待落库的认证记录
|
||||||
|
*/
|
||||||
|
private void validateRecord(TransportCertRecord transportCertRecord) {
|
||||||
|
if (Func.isEmpty(transportCertRecord)) {
|
||||||
|
throw new ServiceException("认证记录不能为空");
|
||||||
|
}
|
||||||
|
if (Func.isEmpty(transportCertRecord.getBizType())) {
|
||||||
|
throw new ServiceException("认证对象类型不能为空");
|
||||||
|
}
|
||||||
|
if (Func.isEmpty(transportCertRecord.getBizId())) {
|
||||||
|
throw new ServiceException("认证对象主键不能为空");
|
||||||
|
}
|
||||||
|
if (Func.isEmpty(transportCertRecord.getSnapshotJson())) {
|
||||||
|
throw new ServiceException("认证内容快照不能为空");
|
||||||
|
}
|
||||||
|
Integer status = transportCertRecord.getStatus();
|
||||||
|
if (Func.isEmpty(status)) {
|
||||||
|
throw new ServiceException("认证记录状态不能为空,不允许依赖框架默认值(默认值 1 会被误判为已通过)");
|
||||||
|
}
|
||||||
|
if (!VALID_STATUS.contains(status)) {
|
||||||
|
throw new ServiceException("认证记录状态取值不正确");
|
||||||
|
}
|
||||||
|
if (TransportCertRecord.STATUS_REJECTED.equals(status) && Func.isEmpty(transportCertRecord.getRejectReason())) {
|
||||||
|
throw new ServiceException("请输入认证驳回原因");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* 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.wrapper;
|
||||||
|
|
||||||
|
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||||
|
import org.springblade.core.tool.utils.BeanUtil;
|
||||||
|
import org.springblade.system.cache.UserCache;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportCertRecord;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportCertRecordVO;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证记录包装类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
public class TransportCertRecordWrapper extends BaseEntityWrapper<TransportCertRecord, TransportCertRecordVO> {
|
||||||
|
|
||||||
|
public static TransportCertRecordWrapper build() {
|
||||||
|
return new TransportCertRecordWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportCertRecordVO entityVO(TransportCertRecord transportCertRecord) {
|
||||||
|
TransportCertRecordVO transportCertRecordVO = Objects.requireNonNull(BeanUtil.copyProperties(transportCertRecord, TransportCertRecordVO.class));
|
||||||
|
transportCertRecordVO.setCreateUserName(UserCache.getUserRealName(transportCertRecord.getCreateUser()));
|
||||||
|
transportCertRecordVO.setUpdateUserName(UserCache.getUserRealName(transportCertRecord.getUpdateUser()));
|
||||||
|
transportCertRecordVO.setSubmitUserName(UserCache.getUserRealName(transportCertRecord.getSubmitUser()));
|
||||||
|
transportCertRecordVO.setAuditUserName(UserCache.getUserRealName(transportCertRecord.getAuditUser()));
|
||||||
|
return transportCertRecordVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
-- 认证记录数据模型(司机 / 车辆共用一张不可变快照表)
|
||||||
|
--
|
||||||
|
-- 背景与设计依据:
|
||||||
|
-- docs/adr/0001-certification-record-immutable-snapshot.md(不可变快照 + 生效指针)
|
||||||
|
-- docs/adr/0002-certification-does-not-block-usage.md(提交不影响可用性)
|
||||||
|
-- 术语定义见根工作区 GLOSSARY.md
|
||||||
|
--
|
||||||
|
-- 本脚本只做 schema 变更,不含任何业务数据订正:
|
||||||
|
-- 1. 新建 blade_transport_cert_record(认证记录,司机与车辆共用,biz_type 区分)
|
||||||
|
-- 2. blade_transport_driver 补认证字段、生效快照指针、身份证有效期
|
||||||
|
-- 3. blade_transport_vehicle 补生效快照指针
|
||||||
|
-- 4. blade_transport_driver 排序规则订正为 utf8mb4_general_ci
|
||||||
|
-- 5. 新增「认证审核-司机 / 车辆」菜单与权限码
|
||||||
|
--
|
||||||
|
-- 幂等:全部语句可重复执行(建表用 IF NOT EXISTS,加列先查 information_schema)。
|
||||||
|
-- MySQL 5.7+ 兼容。
|
||||||
|
--
|
||||||
|
-- ⚠️ 生效快照指针(effective_cert_record_id)的语义:
|
||||||
|
-- "该认证对象是否已认证"的**唯一**判据。为 NULL 表示从未通过审核。
|
||||||
|
-- 一旦被指向,**永不回退为 NULL**——提交、驳回、作废都不得修改它。
|
||||||
|
--
|
||||||
|
-- ⚠️ 留给后续票的两条约束(本脚本无法用 DDL 表达):
|
||||||
|
-- 1) 指针所指记录的 status 必须恒为「已通过」(1),且不得再被改动。
|
||||||
|
-- 这正是不变式 I2 的后半句;由 03 的「已终态记录不可再次审核」保证。
|
||||||
|
-- 2) 指针所指记录的 biz_type / biz_id 必须与持有它的对象一致。
|
||||||
|
-- 由 03 写指针时保证(通过时用的是待审记录自身的 biz_id)。
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- 1. 认证记录表
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS `blade_transport_cert_record` (
|
||||||
|
`id` bigint(20) NOT NULL COMMENT '主键',
|
||||||
|
`tenant_id` varchar(12) NOT NULL DEFAULT '000000' COMMENT '租户ID',
|
||||||
|
`biz_type` varchar(20) NOT NULL COMMENT '认证对象类型:driver-司机,vehicle-车辆',
|
||||||
|
`biz_id` bigint(20) NOT NULL COMMENT '认证对象主键:司机ID或车辆ID(车辆按具体车辆,不是司机)',
|
||||||
|
`biz_key` varchar(100) DEFAULT NULL COMMENT '展示键:司机姓名/车牌号',
|
||||||
|
`status` int(11) NOT NULL COMMENT '审核状态:0审核中 1已通过 2已驳回 3已作废',
|
||||||
|
`snapshot_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '该次提交的全量认证内容JSON,一经创建不可修改',
|
||||||
|
`reject_reason` varchar(200) DEFAULT NULL COMMENT '驳回原因,仅状态为已驳回时有值',
|
||||||
|
`submit_time` datetime DEFAULT NULL COMMENT '提交时间',
|
||||||
|
`submit_user` bigint(20) DEFAULT NULL COMMENT '提交人',
|
||||||
|
`audit_time` datetime DEFAULT NULL COMMENT '审核时间',
|
||||||
|
`audit_user` bigint(20) DEFAULT NULL COMMENT '审核人',
|
||||||
|
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||||
|
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||||
|
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
||||||
|
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除',
|
||||||
|
`pending_biz_id` bigint(20) GENERATED ALWAYS AS (if((`status` = 0) and (`is_deleted` = 0),`biz_id`,NULL)) STORED COMMENT '待审唯一键:仅未删除的审核中记录非空,用于保证同对象至多一条待审快照',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE KEY `uk_cert_record_pending` (`tenant_id`,`biz_type`,`pending_biz_id`) USING BTREE,
|
||||||
|
KEY `idx_cert_record_biz` (`biz_type`,`biz_id`,`submit_time`) USING BTREE,
|
||||||
|
KEY `idx_cert_record_status` (`biz_type`,`status`,`submit_time`) USING BTREE,
|
||||||
|
KEY `idx_cert_record_submit_time` (`submit_time`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='认证记录';
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- 2~4. 加列与排序规则订正
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
DELIMITER $$
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS `upgrade_transport_cert_record_20261002`$$
|
||||||
|
CREATE PROCEDURE `upgrade_transport_cert_record_20261002`()
|
||||||
|
BEGIN
|
||||||
|
DECLARE db_name varchar(128);
|
||||||
|
SET db_name = DATABASE();
|
||||||
|
|
||||||
|
-- ---- 2. 司机表:认证字段 + 生效快照指针 ----
|
||||||
|
-- certification_status 是「最新提交状态」的展示缓存,不是可用性判据;
|
||||||
|
-- 初始为 NULL 表示「从未提交」,与「审核中(0)」语义不同。
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_transport_driver' AND column_name = 'certification_status') THEN
|
||||||
|
ALTER TABLE `blade_transport_driver` ADD COLUMN `certification_status` int(11) DEFAULT NULL COMMENT '最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,NULL为从未提交' AFTER `remark`;
|
||||||
|
END IF;
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_transport_driver' AND column_name = 'certification_reject_reason') THEN
|
||||||
|
ALTER TABLE `blade_transport_driver` ADD COLUMN `certification_reject_reason` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最新一次驳回原因展示缓存' AFTER `certification_status`;
|
||||||
|
END IF;
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_transport_driver' AND column_name = 'effective_cert_record_id') THEN
|
||||||
|
ALTER TABLE `blade_transport_driver` ADD COLUMN `effective_cert_record_id` bigint(20) DEFAULT NULL COMMENT '生效快照指针:指向 blade_transport_cert_record.id,NULL 表示从未认证通过;一经设置永不回退' AFTER `certification_reject_reason`;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- ---- 2.1 司机表:身份证有效期(小程序 person.vue 标为必填,原表缺列)----
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_transport_driver' AND column_name = 'id_card_valid_start') THEN
|
||||||
|
ALTER TABLE `blade_transport_driver` ADD COLUMN `id_card_valid_start` date DEFAULT NULL COMMENT '身份证有效期起' AFTER `id_card_back`;
|
||||||
|
END IF;
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_transport_driver' AND column_name = 'id_card_valid_end') THEN
|
||||||
|
ALTER TABLE `blade_transport_driver` ADD COLUMN `id_card_valid_end` date DEFAULT NULL COMMENT '身份证有效期止' AFTER `id_card_valid_start`;
|
||||||
|
END IF;
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_transport_driver' AND column_name = 'id_card_long_term') THEN
|
||||||
|
ALTER TABLE `blade_transport_driver` ADD COLUMN `id_card_long_term` int(11) DEFAULT '0' COMMENT '身份证长期有效:0否 1是' AFTER `id_card_valid_end`;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- ---- 3. 车辆表:生效快照指针 ----
|
||||||
|
-- 现有 certification_status / certification_reject_reason 保留,降级为「最新提交状态」展示缓存;
|
||||||
|
-- 可用性判定一律改读本指针(改造见 ticket 04)。
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_transport_vehicle' AND column_name = 'effective_cert_record_id') THEN
|
||||||
|
ALTER TABLE `blade_transport_vehicle` ADD COLUMN `effective_cert_record_id` bigint(20) DEFAULT NULL COMMENT '生效快照指针:指向 blade_transport_cert_record.id,NULL 表示从未认证通过;一经设置永不回退' AFTER `certification_reject_reason`;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- ---- 4. 司机表排序规则订正 ----
|
||||||
|
-- blade_transport_driver 建表未显式指定 COLLATE,MySQL 8+ 默认 utf8mb4_0900_ai_ci,
|
||||||
|
-- 与项目规范(utf8mb4_general_ci)不一致;blade_transport_vehicle 曾因此报
|
||||||
|
-- Illegal mix of collations,故一并订正,避免司机认证 JOIN 重演同一故障。
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = db_name AND table_name = 'blade_transport_driver' AND table_collation <> 'utf8mb4_general_ci') THEN
|
||||||
|
ALTER TABLE `blade_transport_driver` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- ---- 4.1 待审唯一键自修复 ----
|
||||||
|
-- 生成列必须同时排除「已逻辑删除」的行:MyBatis-Plus 的 removeById 只把 is_deleted 置 1,
|
||||||
|
-- 不会改 status。若生成列只看 status = 0,一条被逻辑删除的待审记录会继续占用唯一键,
|
||||||
|
-- 导致该认证对象**永远无法再提交**(ticket 02 明确要求车辆认证按逻辑删除处理)。
|
||||||
|
-- 建表语句已是正确版本;这里负责把按早期版本建过表的环境就地修正过来。
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = db_name AND table_name = 'blade_transport_cert_record'
|
||||||
|
AND column_name = 'pending_biz_id' AND generation_expression NOT LIKE '%is_deleted%'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE `blade_transport_cert_record`
|
||||||
|
MODIFY COLUMN `pending_biz_id` bigint(20) GENERATED ALWAYS AS (if((`status` = 0) and (`is_deleted` = 0),`biz_id`,NULL)) STORED COMMENT '待审唯一键:仅未删除的审核中记录非空,用于保证同对象至多一条待审快照';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- ---- 4.2 tenant_id 收紧为 NOT NULL ----
|
||||||
|
-- tenant_id 是待审唯一键 uk_cert_record_pending 的第一列,而 MySQL 的唯一索引**不对 NULL 去重**:
|
||||||
|
-- 若 tenant_id 允许为 NULL,两条 tenant_id IS NULL 的待审记录可以并存,I1 会被整个绕过。
|
||||||
|
-- 本库三张表(driver / vehicle / waybill)实测均无 tenant_id 为 NULL 的行,
|
||||||
|
-- 且「NOT NULL DEFAULT '000000'」是项目既有写法(doc/sql/transport 下 23 个脚本如此)。
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = db_name AND table_name = 'blade_transport_cert_record'
|
||||||
|
AND column_name = 'tenant_id' AND is_nullable = 'YES'
|
||||||
|
) THEN
|
||||||
|
UPDATE `blade_transport_cert_record` SET `tenant_id` = '000000' WHERE `tenant_id` IS NULL;
|
||||||
|
ALTER TABLE `blade_transport_cert_record`
|
||||||
|
MODIFY COLUMN `tenant_id` varchar(12) NOT NULL DEFAULT '000000' COMMENT '租户ID';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- ---- 4.3 车辆认证状态缓存词表对齐 ----
|
||||||
|
-- 改造前车辆侧是「0认证中 1认证通过 2认证驳回」,不含「已作废」;
|
||||||
|
-- 而 02/03 会把「被同对象再次提交取代」写成 3,届时该列无对应语义。
|
||||||
|
-- 取值 0/1/2 在两套词表下含义一致(进行中 / 通过 / 驳回),故这里只补 3 并统一措辞,
|
||||||
|
-- 不改变任何既有数据的含义。仅改列注释(元数据),不动数据。
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = db_name AND table_name = 'blade_transport_vehicle'
|
||||||
|
AND column_name = 'certification_status' AND column_comment NOT LIKE '%已作废%'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE `blade_transport_vehicle`
|
||||||
|
MODIFY COLUMN `certification_status` int(11) DEFAULT '0' COMMENT '最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废;可用性判定只看 effective_cert_record_id';
|
||||||
|
END IF;
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = db_name AND table_name = 'blade_transport_vehicle'
|
||||||
|
AND column_name = 'certification_reject_reason' AND column_comment NOT LIKE '%展示缓存%'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE `blade_transport_vehicle`
|
||||||
|
MODIFY COLUMN `certification_reject_reason` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最新一次驳回原因展示缓存';
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
CALL `upgrade_transport_cert_record_20261002`()$$
|
||||||
|
DROP PROCEDURE `upgrade_transport_cert_record_20261002`$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- 5. 菜单与权限码
|
||||||
|
-- 挂在「运力管理」(2079000000000000001) 下,与司机管理 / 车辆管理同级。
|
||||||
|
-- 权限码按 spec 约定:列表用 cert_certification_audit_{driver|vehicle},
|
||||||
|
-- 审核动作用对应 _audit 后缀。
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES
|
||||||
|
(2079000000000000300, 2079000000000000001, 'cert_certification_audit_driver', '认证审核-司机', 'cert_certification_audit_driver', '/transportCapacity/cert-audit-driver', 'iconfont iconicon_doc', 4, 1, 0, 1, '', '司机认证待审列表', 0),
|
||||||
|
(2079000000000000301, 2079000000000000300, 'cert_certification_audit_driver_audit', '审核', 'cert_certification_audit_driver_audit', '', '', 1, 2, 0, 1, NULL, '通过/驳回司机认证', 0),
|
||||||
|
(2079000000000000310, 2079000000000000001, 'cert_certification_audit_vehicle', '认证审核-车辆', 'cert_certification_audit_vehicle', '/transportCapacity/cert-audit-vehicle', 'iconfont iconicon_doc', 5, 1, 0, 1, '', '车辆认证待审列表', 0),
|
||||||
|
(2079000000000000311, 2079000000000000310, 'cert_certification_audit_vehicle_audit', '审核', 'cert_certification_audit_vehicle_audit', '', '', 1, 2, 0, 1, NULL, '通过/驳回车辆认证', 0)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
`parent_id` = VALUES(`parent_id`),
|
||||||
|
`code` = VALUES(`code`),
|
||||||
|
`name` = VALUES(`name`),
|
||||||
|
`alias` = VALUES(`alias`),
|
||||||
|
`path` = VALUES(`path`),
|
||||||
|
`source` = VALUES(`source`),
|
||||||
|
`sort` = VALUES(`sort`),
|
||||||
|
`category` = VALUES(`category`),
|
||||||
|
`action` = VALUES(`action`),
|
||||||
|
`is_open` = VALUES(`is_open`),
|
||||||
|
`component` = VALUES(`component`),
|
||||||
|
`remark` = VALUES(`remark`),
|
||||||
|
`is_deleted` = VALUES(`is_deleted`);
|
||||||
@@ -17,6 +17,9 @@ CREATE TABLE `blade_transport_driver` (
|
|||||||
`posts` varchar(100) DEFAULT NULL COMMENT '岗位,多个使用逗号分隔',
|
`posts` varchar(100) DEFAULT NULL COMMENT '岗位,多个使用逗号分隔',
|
||||||
`id_card_front` varchar(1000) DEFAULT NULL COMMENT '身份证正面照',
|
`id_card_front` varchar(1000) DEFAULT NULL COMMENT '身份证正面照',
|
||||||
`id_card_back` varchar(1000) DEFAULT NULL COMMENT '身份证反面照',
|
`id_card_back` varchar(1000) DEFAULT NULL COMMENT '身份证反面照',
|
||||||
|
`id_card_valid_start` date DEFAULT NULL COMMENT '身份证有效期起',
|
||||||
|
`id_card_valid_end` date DEFAULT NULL COMMENT '身份证有效期止',
|
||||||
|
`id_card_long_term` int(11) DEFAULT '0' COMMENT '身份证长期有效:0否 1是',
|
||||||
`head_photo` varchar(1000) DEFAULT NULL COMMENT '大头照',
|
`head_photo` varchar(1000) DEFAULT NULL COMMENT '大头照',
|
||||||
`driving_type` varchar(20) NOT NULL COMMENT '准驾车型',
|
`driving_type` varchar(20) NOT NULL COMMENT '准驾车型',
|
||||||
`driving_license_no` varchar(50) DEFAULT NULL COMMENT '驾驶证档案编号',
|
`driving_license_no` varchar(50) DEFAULT NULL COMMENT '驾驶证档案编号',
|
||||||
@@ -39,6 +42,9 @@ CREATE TABLE `blade_transport_driver` (
|
|||||||
`emergency_contact_name` varchar(20) NOT NULL COMMENT '紧急联系人姓名',
|
`emergency_contact_name` varchar(20) NOT NULL COMMENT '紧急联系人姓名',
|
||||||
`emergency_contact_mobile` varchar(20) NOT NULL COMMENT '紧急联系人手机号',
|
`emergency_contact_mobile` varchar(20) NOT NULL COMMENT '紧急联系人手机号',
|
||||||
`remark` varchar(200) DEFAULT NULL COMMENT '备注',
|
`remark` varchar(200) DEFAULT NULL COMMENT '备注',
|
||||||
|
`certification_status` int(11) DEFAULT NULL COMMENT '最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,NULL为从未提交',
|
||||||
|
`certification_reject_reason` varchar(200) DEFAULT NULL COMMENT '最新一次驳回原因展示缓存',
|
||||||
|
`effective_cert_record_id` bigint(20) DEFAULT NULL COMMENT '生效快照指针:指向 blade_transport_cert_record.id,NULL 表示从未认证通过;一经设置永不回退',
|
||||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
@@ -54,7 +60,7 @@ CREATE TABLE `blade_transport_driver` (
|
|||||||
KEY `idx_transport_driver_license_end` (`driving_license_end_date`) USING BTREE,
|
KEY `idx_transport_driver_license_end` (`driving_license_end_date`) USING BTREE,
|
||||||
KEY `idx_transport_driver_qualification_end` (`qualification_end_date`) USING BTREE,
|
KEY `idx_transport_driver_qualification_end` (`qualification_end_date`) USING BTREE,
|
||||||
KEY `idx_transport_driver_status` (`status`) USING BTREE
|
KEY `idx_transport_driver_status` (`status`) USING BTREE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司机管理';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='司机管理';
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Records of blade_menu for transport capacity driver
|
-- Records of blade_menu for transport capacity driver
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ CREATE TABLE `blade_transport_vehicle` (
|
|||||||
`registration_date` date DEFAULT NULL COMMENT '机动车登记日期',
|
`registration_date` date DEFAULT NULL COMMENT '机动车登记日期',
|
||||||
`registration_image` varchar(1000) DEFAULT NULL COMMENT '机动车登记本图片',
|
`registration_image` varchar(1000) DEFAULT NULL COMMENT '机动车登记本图片',
|
||||||
`remark` varchar(200) DEFAULT NULL COMMENT '备注',
|
`remark` varchar(200) DEFAULT NULL COMMENT '备注',
|
||||||
`certification_status` int(11) DEFAULT '0' COMMENT '认证状态:0认证中 1认证通过 2认证驳回',
|
`certification_status` int(11) DEFAULT '0' COMMENT '最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废;可用性判定只看 effective_cert_record_id',
|
||||||
`certification_reject_reason` varchar(200) DEFAULT NULL COMMENT '认证驳回原因',
|
`certification_reject_reason` varchar(200) DEFAULT NULL COMMENT '最新一次驳回原因展示缓存',
|
||||||
|
`effective_cert_record_id` bigint(20) DEFAULT NULL COMMENT '生效快照指针:指向 blade_transport_cert_record.id,NULL 表示从未认证通过;一经设置永不回退',
|
||||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
|||||||
Reference in New Issue
Block a user