🔀 合并司机档案迁移:可空身份证号与账号绑定唯一索引

This commit is contained in:
2026-10-23 15:37:50 +08:00
2 changed files with 152 additions and 1 deletions
+4 -1
View File
@@ -6,7 +6,7 @@ CREATE TABLE `blade_transport_driver` (
`id` bigint(20) NOT NULL COMMENT '主键',
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
`driver_name` varchar(20) NOT NULL COMMENT '司机姓名',
`id_card_no` varchar(18) NOT NULL COMMENT '身份证号',
`id_card_no` varchar(18) DEFAULT NULL COMMENT '身份证号,NULL 表示尚未采集',
`birthday` date DEFAULT NULL COMMENT '出生年月',
`gender` varchar(10) DEFAULT NULL COMMENT '性别',
`nation` varchar(20) DEFAULT NULL COMMENT '民族',
@@ -37,6 +37,8 @@ CREATE TABLE `blade_transport_driver` (
`driver_type` varchar(20) NOT NULL COMMENT '司机类型:自有/外协',
`mobile` varchar(20) NOT NULL COMMENT '手机号',
`user_id` bigint(20) DEFAULT NULL COMMENT '关联系统用户ID',
`profile_source` varchar(20) DEFAULT NULL COMMENT 'login_auto/login_bound/admin/cert_backfill,NULL 为改造前存量',
`account_bound_time` datetime DEFAULT NULL COMMENT '档案与账号建立关联的时点',
`contact_relation` varchar(20) DEFAULT NULL COMMENT '与联系人关系',
`organization_name` varchar(50) NOT NULL COMMENT '所属组织',
`emergency_contact_name` varchar(20) NOT NULL COMMENT '紧急联系人姓名',
@@ -54,6 +56,7 @@ CREATE TABLE `blade_transport_driver` (
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_transport_driver_id_card` (`id_card_no`) USING BTREE,
UNIQUE KEY `uk_transport_driver_user` (`user_id`) USING BTREE,
KEY `idx_transport_driver_mobile` (`mobile`) USING BTREE,
KEY `idx_transport_driver_type` (`driver_type`) USING BTREE,
KEY `idx_transport_driver_org` (`organization_name`) USING BTREE,
@@ -0,0 +1,148 @@
-- ----------------------------
-- 司机档案与登录账号打通(2026-10-23)
-- ----------------------------
-- 三处结构变更,均在单据「07 数据模型迁移」内:
-- ① user_id 加唯一索引 uk_transport_driver_user —— 一个账号只能对应一条司机档案。
-- ② id_card_no 放宽为可空 —— NULL 表示「身份证号尚未采集」,是判断档案是否完善的口径。
-- uk_transport_driver_id_card 保留:MySQL 唯一索引对多行 NULL 不去重,
-- 并发创建多个占位档案不会被唯一键挡住。
-- ③ 新增 profile_source / account_bound_time 两列留痕:
-- profile_source 取值 login_auto / login_bound / admin / cert_backfill,
-- NULL 表示改造前存量(来源未知,不臆造);account_bound_time 是档案与账号建立关联的时点。
--
-- 只做结构变更,不回填数据:存量档案的 profile_source / account_bound_time 一律保持 NULL。
-- 判定「档案是否采集」用 id_card_no IS NULL,不另加标记列(见票 01)。
--
-- 幂等:每段先查 information_schema,条件已满足即跳过,重复执行不报错;
-- 全部用 PREPARE 动态语句而非存储过程,因此不需要 DELIMITER——按分号逐句切分的客户端
-- (含数据库驱动)也能直接执行。
--
-- 前置检查(user_id 有重复时,第 1 节会以
-- Duplicate entry '<值>' for key 'blade_transport_driver.uk_transport_driver_user'
-- 失败并终止;此时本脚本对表零修改,先修数据再重跑):
-- 唯一索引统计**全表**行,含 is_deleted = 1 的软删除档案。票面给的 is_deleted = 0 口径
-- 只能发现活跃重复,软删除行之间的重复同样会让第 1 节失败:
-- SELECT user_id, COUNT(*) AS dup_count FROM `blade_transport_driver`
-- WHERE user_id IS NOT NULL
-- GROUP BY user_id HAVING COUNT(*) > 1;
-- 修复口径:一个 user_id 只保留一条档案,其余把 user_id 置 NULL(不要改 id / is_deleted),
-- 确认无误后重跑本脚本。
--
-- 注意:uk_transport_driver_user 覆盖软删除行 —— is_deleted = 1 的档案仍占用 user_id,
-- 后台软删除某账号的档案后,该账号不能再建第二条档案(会撞唯一键);
-- 需要释放时,软删除前先把该行的 user_id 置 NULL。
SET @transport_driver_schema = DATABASE();
-- ----------------------------
-- 0. 表存在性守卫
-- ----------------------------
-- 表不存在时故意执行一条只读查询,让客户端报 1146「Table ... doesn't exist」;
-- 否则下面每段都因查不到列/索引而静默跳过,脚本会假绿。
SET @transport_driver_guard_sql = (
SELECT IF(COUNT(*) > 0,
'SELECT 1',
'SELECT 1 FROM `blade_transport_driver` LIMIT 0')
FROM information_schema.tables
WHERE table_schema = @transport_driver_schema
AND table_name = 'blade_transport_driver'
);
PREPARE transport_driver_guard_stmt FROM @transport_driver_guard_sql;
EXECUTE transport_driver_guard_stmt;
DEALLOCATE PREPARE transport_driver_guard_stmt;
-- ----------------------------
-- 1. 账号唯一索引(先行执行:有重复时立刻失败,脚本对表零修改)
-- ----------------------------
-- 只读诊断:有输出即为重复清单,先按上面口径修数据再往下跑。
SELECT `user_id`, COUNT(*) AS `dup_count`
FROM `blade_transport_driver`
WHERE `user_id` IS NOT NULL
GROUP BY `user_id`
HAVING COUNT(*) > 1;
-- 同名索引若已存在但是普通索引(non_unique = 1),本段会以
-- Duplicate key name 'uk_transport_driver_user' 失败:先 DROP INDEX 再重跑本脚本。
SET @transport_driver_user_index_sql = (
SELECT IF(COUNT(*) = 0,
'ALTER TABLE `blade_transport_driver` ADD UNIQUE KEY `uk_transport_driver_user` (`user_id`) USING BTREE',
'SELECT 1')
FROM information_schema.statistics
WHERE table_schema = @transport_driver_schema
AND table_name = 'blade_transport_driver'
AND index_name = 'uk_transport_driver_user'
AND non_unique = 0
);
PREPARE transport_driver_user_index_stmt FROM @transport_driver_user_index_sql;
EXECUTE transport_driver_user_index_stmt;
DEALLOCATE PREPARE transport_driver_user_index_stmt;
-- ----------------------------
-- 2. 身份证号放宽为可空(唯一索引 uk_transport_driver_id_card 保留)
-- ----------------------------
-- 条件写成「非空 或 长度不是 18」而不是只看 is_nullable:
-- 该列历史上曾被截短/加长过,只判可空性会漏掉类型漂移。
SET @transport_driver_id_card_sql = (
SELECT IF(COUNT(*) > 0,
'ALTER TABLE `blade_transport_driver` MODIFY COLUMN `id_card_no` varchar(18) NULL COMMENT ''身份证号,NULL 表示尚未采集''',
'SELECT 1')
FROM information_schema.columns
WHERE table_schema = @transport_driver_schema
AND table_name = 'blade_transport_driver'
AND column_name = 'id_card_no'
AND (is_nullable = 'NO' OR character_maximum_length <> 18)
);
PREPARE transport_driver_id_card_stmt FROM @transport_driver_id_card_sql;
EXECUTE transport_driver_id_card_stmt;
DEALLOCATE PREPARE transport_driver_id_card_stmt;
-- ----------------------------
-- 3. 来源留痕列:profile_source
-- ----------------------------
SET @transport_driver_profile_source_sql = (
SELECT IF(COUNT(*) = 0,
'ALTER TABLE `blade_transport_driver` ADD COLUMN `profile_source` varchar(20) DEFAULT NULL COMMENT ''login_auto/login_bound/admin/cert_backfill,NULL 为改造前存量'' AFTER `user_id`',
'SELECT 1')
FROM information_schema.columns
WHERE table_schema = @transport_driver_schema
AND table_name = 'blade_transport_driver'
AND column_name = 'profile_source'
);
PREPARE transport_driver_profile_source_stmt FROM @transport_driver_profile_source_sql;
EXECUTE transport_driver_profile_source_stmt;
DEALLOCATE PREPARE transport_driver_profile_source_stmt;
-- ----------------------------
-- 4. 绑定留痕列:account_bound_time
-- ----------------------------
SET @transport_driver_account_bound_time_sql = (
SELECT IF(COUNT(*) = 0,
'ALTER TABLE `blade_transport_driver` ADD COLUMN `account_bound_time` datetime DEFAULT NULL COMMENT ''档案与账号建立关联的时点'' AFTER `profile_source`',
'SELECT 1')
FROM information_schema.columns
WHERE table_schema = @transport_driver_schema
AND table_name = 'blade_transport_driver'
AND column_name = 'account_bound_time'
);
PREPARE transport_driver_account_bound_time_stmt FROM @transport_driver_account_bound_time_sql;
EXECUTE transport_driver_account_bound_time_stmt;
DEALLOCATE PREPARE transport_driver_account_bound_time_stmt;
-- ----------------------------
-- 5. 回滚
-- ----------------------------
-- 顺序与正向相反:先删索引与两列,最后把 id_card_no 收回 NOT NULL。
-- 收窄之前必须先清掉 NULL,否则 ALTER 会以
-- Invalid use of NULL value / Data truncated for column 'id_card_no' 失败。
-- 占位档案的身份证号没有真实值可还原,回滚前须业务先决定「删档案」还是「补占位串」:
-- -- ① 处理 NULL(示例为删除登录自动建的占位档案;改为 UPDATE 成占位串亦可,注意会占唯一索引)
-- DELETE FROM `blade_transport_driver`
-- WHERE `id_card_no` IS NULL AND `profile_source` IN ('login_auto', 'login_bound');
-- -- ② 收回身份证号非空与旧注释
-- ALTER TABLE `blade_transport_driver`
-- MODIFY COLUMN `id_card_no` varchar(18) NOT NULL COMMENT '身份证号';
-- -- ③ 删唯一索引、删两列
-- ALTER TABLE `blade_transport_driver` DROP INDEX `uk_transport_driver_user`;
-- ALTER TABLE `blade_transport_driver` DROP COLUMN `profile_source`, DROP COLUMN `account_bound_time`;
--
-- uk_transport_driver_id_card 全程保留,回滚不需要恢复它。