From 87f4c3a830e18f3240cd641ef81f901f085ef2d8 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Fri, 2 Oct 2026 14:40:56 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E8=AE=B0=E5=BD=95=E4=B8=8D=E5=8F=AF=E5=8F=98=E5=BF=AB?= =?UTF-8?q?=E7=85=A7=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9E=8B=E4=B8=8E=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 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 请求体自行「认证」自己; 同步基线建表脚本与认证审核菜单/权限码。本票不含任何业务接口。 --- .../transport/pojo/entity/Driver.java | 44 ++++ .../pojo/entity/TransportCertRecord.java | 156 +++++++++++++++ .../pojo/entity/TransportVehicle.java | 19 ++ .../pojo/vo/TransportCertRecordVO.java | 74 +++++++ .../transport/mapper/DriverMapper.xml | 14 +- .../mapper/TransportCertRecordMapper.java | 38 ++++ .../mapper/TransportVehicleMapper.xml | 2 + .../service/ITransportCertRecordService.java | 62 ++++++ .../impl/TransportCertRecordServiceImpl.java | 188 +++++++++++++++++ .../wrapper/TransportCertRecordWrapper.java | 57 ++++++ .../blade_transport_cert_record_20261002.sql | 189 ++++++++++++++++++ doc/sql/transport/blade_transport_driver.sql | 8 +- doc/sql/transport/blade_transport_vehicle.sql | 5 +- 13 files changed, 852 insertions(+), 4 deletions(-) create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportCertRecord.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportCertRecordVO.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/TransportCertRecordMapper.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/service/ITransportCertRecordService.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportCertRecordServiceImpl.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/TransportCertRecordWrapper.java create mode 100644 doc/sql/transport/blade_transport_cert_record_20261002.sql diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Driver.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Driver.java index 1d13e2ec..80137624 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Driver.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Driver.java @@ -28,6 +28,9 @@ 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.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 lombok.Data; import lombok.EqualsAndHashCode; @@ -111,6 +114,21 @@ public class Driver extends TenantEntity { */ @Schema(description = "身份证反面照") 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 = "备注") private String remark; + /** + * 最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null 表示从未提交 + *

+ * 仅为列表展示而冗余,不是可用性判据;「是否已认证」一律读生效快照指针。 + */ + @Schema(description = "最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废,null为从未提交") + private Integer certificationStatus; + /** + * 最新一次驳回原因展示缓存 + */ + @Schema(description = "最新一次驳回原因展示缓存") + private String certificationRejectReason; + /** + * 生效快照指针:指向 blade_transport_cert_record.id + *

+ * 「该司机是否已认证」的唯一判据。为 null 表示从未认证通过; + * 一经设置永不回退——提交、驳回、作废都不得修改它(见 ADR-0002)。 + *

+ * ⚠️ {@code READ_ONLY}:本字段只能由审核链路(03)写入。 + * 司机档案的 {@code /submit} 直接以本实体接收请求体,若允许反序列化, + * 调用方就能自行填一个记录 ID 把自己「认证」掉,绕过审核与 D10 的可用性门槛。 + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "生效快照指针") + private Long effectiveCertRecordId; } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportCertRecord.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportCertRecord.java new file mode 100644 index 00000000..93d0aab7 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportCertRecord.java @@ -0,0 +1,156 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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; + +/** + * 认证记录实体类 + *

+ * 认证对象(司机 / 车辆)每一次提交所产生的、不可变的快照。 + * 记录一经创建,其 {@code snapshotJson} 与提交元信息永不修改;被同对象的后续提交取代时 + * 标记为「已作废」,而不是删除。判定某个对象「是否已认证」只看它持有的生效快照指针, + * 与记录表里的任何一条记录的状态都无关。 + *

+ * 设计依据:{@code docs/adr/0001-certification-record-immutable-snapshot.md}, + * 术语见根工作区 {@code GLOSSARY.md}。 + *

+ * ⚠️ {@code status} 承载的是「审核状态」,不是 BladeX 的「启用 / 停用」。 + * 该字段继承自 {@link TenantEntity},带来两条必须堵住的框架通道: + * 一是 {@code BaseServiceImpl#resolveEntity} 在插入时会把 {@code null} 的 {@code status} + * 静默填成 {@code 1}(恰好是 {@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-车辆 + *

+ * {@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),一经创建不可修改 + *

+ * {@code NEVER}:这是不变式 I3 的结构性保证——本字段永不进入 UPDATE 语句, + * 因此不存在「某条链路忘了保护快照」的可能。改内容只能靠新提交一条记录。 + */ + @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; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportVehicle.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportVehicle.java index 4829cc33..d18a3008 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportVehicle.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/TransportVehicle.java @@ -26,6 +26,9 @@ package org.springblade.transport.pojo.entity; 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 lombok.Data; import lombok.EqualsAndHashCode; @@ -224,5 +227,21 @@ public class TransportVehicle extends TenantEntity { /** 认证驳回原因 */ @Schema(description = "认证驳回原因") private String certificationRejectReason; + /** + * 生效快照指针:指向 blade_transport_cert_record.id + *

+ * 「该车辆是否已认证」的唯一判据。为 null 表示从未认证通过; + * 一经设置永不回退——重新提交、待审、被驳回都不得修改它(见 ADR-0002)。 + * 改造后 {@link #certificationStatus} 降级为「最新提交状态」的展示缓存。 + *

+ * ⚠️ {@code READ_ONLY}:本字段只能由审核链路(03)写入。 + * {@code POST /transport-vehicle/submit} 直接以本实体接收请求体, + * 若允许反序列化,调用方就能自行填一个记录 ID 把自己「认证」掉, + * 从而完全绕过审核与 D10 的可用性门槛。 + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "生效快照指针") + private Long effectiveCertRecordId; } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportCertRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportCertRecordVO.java new file mode 100644 index 00000000..8275d7be --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportCertRecordVO.java @@ -0,0 +1,74 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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; + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/DriverMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/DriverMapper.xml index 45ad067b..89e87067 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/DriverMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/DriverMapper.xml @@ -46,6 +46,12 @@ + + + + + + @@ -70,6 +76,9 @@ posts, id_card_front, id_card_back, + id_card_valid_start, + id_card_valid_end, + id_card_long_term, head_photo, driving_type, driving_license_no, @@ -91,7 +100,10 @@ organization_name, emergency_contact_name, emergency_contact_mobile, - remark + remark, + certification_status, + certification_reject_reason, + effective_cert_record_id