✨ 新增认证记录不可变快照数据模型与迁移脚本
按 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:
@@ -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 '岗位,多个使用逗号分隔',
|
||||
`id_card_front` 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 '大头照',
|
||||
`driving_type` varchar(20) NOT 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_mobile` varchar(20) NOT 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_dept` bigint(20) 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_qualification_end` (`qualification_end_date`) 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
|
||||
|
||||
@@ -39,8 +39,9 @@ CREATE TABLE `blade_transport_vehicle` (
|
||||
`registration_date` date DEFAULT NULL COMMENT '机动车登记日期',
|
||||
`registration_image` varchar(1000) DEFAULT NULL COMMENT '机动车登记本图片',
|
||||
`remark` varchar(200) DEFAULT NULL COMMENT '备注',
|
||||
`certification_status` int(11) DEFAULT '0' COMMENT '认证状态:0认证中 1认证通过 2认证驳回',
|
||||
`certification_reject_reason` varchar(200) DEFAULT NULL COMMENT '认证驳回原因',
|
||||
`certification_status` int(11) DEFAULT '0' COMMENT '最新提交状态展示缓存:0审核中 1已通过 2已驳回 3已作废;可用性判定只看 effective_cert_record_id',
|
||||
`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_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
|
||||
Reference in New Issue
Block a user