✨ 新增存量司机/车辆补生效快照的订正脚本
「能否接单」的判据是生效快照指针,该列随认证门槛一起新增时对存量对象一律为 NULL,门槛默认开启后存量司机与车辆全部被判未认证、接不了单。脚本给尚无快照的对象补一条已通过记录并把指针指过去,含备份、校验与回退;幂等,且能在已订正过的库上续跑(从段内最大号之后接着发号,不撞主键)。
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
-- ----------------------------
|
||||
-- 认证:存量司机 / 车辆补生效快照
|
||||
-- 写业务数据。执行前必须先建备份表(第 0 节),执行后必须跑第 3 节校验。可重复执行。
|
||||
-- ----------------------------
|
||||
--
|
||||
-- 为什么必须补:
|
||||
-- 「能否接单」在运营状态之上叠加了认证门槛,判据是生效快照指针
|
||||
-- blade_transport_driver.effective_cert_record_id 与
|
||||
-- blade_transport_vehicle.effective_cert_record_id。这两列与门槛一起新增,
|
||||
-- 新增时对存量对象一律为 NULL;门槛默认开启,于是存量司机与车辆全部被判成
|
||||
-- 「未认证」,接单被直接拒绝。它们在此之前一直正常承运,可用性是既成事实,
|
||||
-- 不该因为换了判定方式就丢掉。
|
||||
--
|
||||
-- 处置口径:给尚无生效快照的存量对象补一条「已通过」记录,并把指针指过去。
|
||||
-- 这是自动认可、不是任何人的审核行为,故 audit_user / audit_time 一律留空,
|
||||
-- 不凭空编造审核留痕。
|
||||
--
|
||||
-- 快照键名必须与 PersonCertSubmitRequest / VehicleCertSubmitRequest 完全一致。
|
||||
-- 键名对不上不会报错、只会静默丢值:司机端「重新认证」表单会缺字段,
|
||||
-- 审核详情会把每个字段都报成「已变更」。改 DTO 字段名时务必同步本脚本。
|
||||
--
|
||||
-- 司机的从业资格证有效期起(qualificationValidStart)在快照里没有来源:
|
||||
-- blade_transport_driver 没有对应列,管理端的同名录入项也是只收不存。
|
||||
-- 此处同样不造值,该键留空。
|
||||
--
|
||||
-- 司机快照里没有 mobile:审核列表的司机姓名/手机号取的是司机档案当前值,
|
||||
-- 不是提交时的快照值,故无需写进快照。
|
||||
--
|
||||
-- 提交司机(submit_user)为什么一起补:
|
||||
-- 审核列表的「司机姓名 / 司机手机号」取数路径是 submit_user -> 系统账号 -> 司机档案。
|
||||
-- submit_user 为空,这两列就永远空白,审核队列「找得到人、联系得上」的价值就没了。
|
||||
-- 车辆认证记录本身不含司机信息,其提交司机取「该车当前绑定的司机」——
|
||||
-- 车辆认证的提交链路强制校验只能操作本人绑定的车辆,故真实提交的车辆认证记录,
|
||||
-- 其提交司机就是这辆车的绑定司机。一条车牌有多个绑定司机时
|
||||
-- (driving_vehicle 是逗号分隔的文本列、无唯一索引),取司机档案 update_time 最新者:
|
||||
-- 库里没有绑定时间戳,这是可用的最好代理指标。
|
||||
-- 解析不到系统账号的(该司机根本没有 blade_user),留 NULL,不编造。
|
||||
--
|
||||
-- 号段:blade_transport_cert_record.id 由应用侧雪花算法发号(当前约 2.1e18),
|
||||
-- 本脚本自行发号,必须落在应用永远到不了的区间,否则会与真实提交撞主键。
|
||||
-- 车辆与司机各占一段,段距 1e17,互不重叠,且都远低于 bigint 上限 9.22e18。
|
||||
-- 该号段同时是回退的识别依据(第 4 节)。
|
||||
-- ⚠️ 不要改成「取 MAX(id) + 偏移量」:雪花 id 每秒最多 +1000,
|
||||
-- 取 MAX+1000000 的话应用约 17 分钟就能追平并开始撞号。
|
||||
-- ⚠️ 也不要把 id 读出来再拼 UPDATE 的 WHERE:id 约 2.1e18 超过 JS/JSON
|
||||
-- 安全整数上限 2^53,传回数据库时末位被截断,UPDATE 会静默匹配 0 行——
|
||||
-- 记录插进去了、指针却没写,且不报任何错。故全程用 SQL 自关联
|
||||
-- (JOIN ... ON r.biz_id = v.id),id 只在库内流转。
|
||||
--
|
||||
-- 发号用会话变量而不是 ROW_NUMBER():窗口函数要 MySQL 8.0 起,本脚本按 5.7 也能执行写。
|
||||
--
|
||||
-- 前置条件:blade_transport_cert_record 表与两张业务表上的
|
||||
-- certification_status / certification_reject_reason / effective_cert_record_id
|
||||
-- 列都已存在(见 blade_transport_cert_record_20261002.sql)。缺表时报
|
||||
-- 「Table ... doesn't exist」,先补跑那个脚本再执行本脚本。
|
||||
|
||||
-- ----------------------------
|
||||
-- 0. 备份(必须在订正之前执行,仅首次需要)
|
||||
-- ----------------------------
|
||||
-- CREATE TABLE blade_transport_vehicle_certbak AS
|
||||
-- SELECT id, certification_status, certification_reject_reason, effective_cert_record_id
|
||||
-- FROM blade_transport_vehicle;
|
||||
-- CREATE TABLE blade_transport_driver_certbak AS
|
||||
-- SELECT id, certification_status, certification_reject_reason, effective_cert_record_id
|
||||
-- FROM blade_transport_driver;
|
||||
-- CREATE TABLE blade_transport_cert_record_bak AS SELECT * FROM blade_transport_cert_record;
|
||||
|
||||
START TRANSACTION;
|
||||
|
||||
-- ----------------------------
|
||||
-- 1. 为尚无生效快照的车辆补「已通过」记录
|
||||
-- ----------------------------
|
||||
-- 从段内已有的最大号之后接着发,而不是每次从段首发:
|
||||
-- 订正过一次之后又导入了新车、再跑本脚本时,从段首发号会与已有记录撞主键。
|
||||
-- 变量存的是**段内序号**(段基址已减掉),下面 SELECT 里再加回段基址。
|
||||
SET @vehicle_seq := (SELECT IFNULL(MAX(id) - 9000000000000000000, 0)
|
||||
FROM blade_transport_cert_record
|
||||
WHERE id >= 9000000000000000000 AND id < 9100000000000000000);
|
||||
|
||||
INSERT INTO blade_transport_cert_record
|
||||
(id, tenant_id, biz_type, biz_id, biz_key, status, snapshot_json,
|
||||
submit_time, submit_user, create_time, update_time, is_deleted)
|
||||
SELECT 9000000000000000000 + (@vehicle_seq := @vehicle_seq + 1),
|
||||
IFNULL(v.tenant_id, '000000'), 'vehicle', v.id, IFNULL(v.plate_no, ''), 1,
|
||||
JSON_OBJECT(
|
||||
'plateNo', v.plate_no,
|
||||
'ownerName', v.owner_name,
|
||||
'plateColor', v.plate_color,
|
||||
'vehicleType', v.vehicle_type,
|
||||
'vin', v.vin,
|
||||
'outerLength', v.outer_length,
|
||||
'outerWidth', v.outer_width,
|
||||
'outerHeight', v.outer_height,
|
||||
'approvedLoadKg', v.approved_load_kg,
|
||||
'tractionMassKg', v.traction_mass_kg,
|
||||
'energyType', v.energy_type,
|
||||
'businessRelation', v.business_relation,
|
||||
'compulsoryScrapDate', v.compulsory_scrap_date,
|
||||
'compulsoryScrapLongTerm', IF(v.compulsory_scrap_long_term = 1, 1, 0),
|
||||
'drivingLicenseNo', v.driving_license_no,
|
||||
'drivingLicenseImage', v.driving_license_image,
|
||||
'drivingLicenseMainBack', v.driving_license_main_back,
|
||||
'drivingLicenseViceFront', v.driving_license_vice_front,
|
||||
'drivingLicenseViceBack', v.driving_license_vice_back,
|
||||
'drivingLicenseStartDate', v.driving_license_start_date,
|
||||
'drivingLicenseEndDate', v.driving_license_end_date,
|
||||
'drivingLicenseLongTerm', IF(v.driving_license_long_term = 1, 1, 0),
|
||||
'roadTransportCertNo', v.road_transport_cert_no,
|
||||
'roadTransportCertImage', v.road_transport_cert_image,
|
||||
'roadTransportCertStartDate', v.road_transport_cert_start_date,
|
||||
'roadTransportCertEndDate', v.road_transport_cert_end_date,
|
||||
'roadTransportCertLongTerm', IF(v.road_transport_cert_long_term = 1, 1, 0),
|
||||
'registrationNo', v.registration_no,
|
||||
'registrationImage', v.registration_image
|
||||
),
|
||||
NOW(),
|
||||
(SELECT dr.user_id
|
||||
FROM blade_transport_driver dr
|
||||
WHERE dr.is_deleted = 0
|
||||
AND dr.user_id IS NOT NULL
|
||||
AND dr.driving_vehicle IS NOT NULL
|
||||
AND FIND_IN_SET(v.plate_no, REPLACE(dr.driving_vehicle, ',', ',')) > 0
|
||||
ORDER BY dr.update_time DESC, dr.id DESC
|
||||
LIMIT 1),
|
||||
NOW(), NOW(), 0
|
||||
FROM blade_transport_vehicle v
|
||||
LEFT JOIN blade_transport_cert_record r
|
||||
ON r.biz_type = 'vehicle' AND r.biz_id = v.id
|
||||
AND r.id >= 9000000000000000000 AND r.id < 9100000000000000000
|
||||
WHERE v.is_deleted = 0
|
||||
AND v.effective_cert_record_id IS NULL
|
||||
AND r.id IS NULL
|
||||
ORDER BY v.id;
|
||||
|
||||
-- 回填指针:靠 biz_id 自关联,不把 id 带出数据库
|
||||
UPDATE blade_transport_vehicle v
|
||||
JOIN blade_transport_cert_record r
|
||||
ON r.biz_type = 'vehicle' AND r.biz_id = v.id
|
||||
AND r.id >= 9000000000000000000 AND r.id < 9100000000000000000 AND r.status = 1
|
||||
SET v.effective_cert_record_id = r.id,
|
||||
v.certification_status = 1,
|
||||
v.certification_reject_reason = NULL
|
||||
WHERE v.effective_cert_record_id IS NULL AND v.is_deleted = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- 2. 为尚无生效快照的司机补「已通过」记录
|
||||
-- ----------------------------
|
||||
SET @driver_seq := (SELECT IFNULL(MAX(id) - 9100000000000000000, 0)
|
||||
FROM blade_transport_cert_record
|
||||
WHERE id >= 9100000000000000000 AND id < 9200000000000000000);
|
||||
|
||||
INSERT INTO blade_transport_cert_record
|
||||
(id, tenant_id, biz_type, biz_id, biz_key, status, snapshot_json,
|
||||
submit_time, submit_user, create_time, update_time, is_deleted)
|
||||
SELECT 9100000000000000000 + (@driver_seq := @driver_seq + 1),
|
||||
IFNULL(d.tenant_id, '000000'), 'driver', d.id, IFNULL(d.driver_name, ''), 1,
|
||||
JSON_OBJECT(
|
||||
'realName', d.driver_name,
|
||||
'idCardNo', d.id_card_no,
|
||||
'idCardFrontUrl', d.id_card_front,
|
||||
'idCardBackUrl', d.id_card_back,
|
||||
'idCardValidStart', d.id_card_valid_start,
|
||||
'idCardValidEnd', d.id_card_valid_end,
|
||||
'idCardLongTerm', IF(d.id_card_long_term = 1, 1, 0),
|
||||
'driverFileNo', d.driving_license_no,
|
||||
'driverClass', d.driving_type,
|
||||
'driverLicenseFrontUrl', d.driving_license_front,
|
||||
'driverLicenseBackUrl', d.driving_license_back,
|
||||
'driverValidStart', d.driving_license_start_date,
|
||||
'driverValidEnd', d.driving_license_end_date,
|
||||
'driverLongTerm', IF(d.driving_license_long_term = 1, 1, 0),
|
||||
'qualificationType', d.qualification_type,
|
||||
'qualificationNo', d.qualification_no,
|
||||
'qualificationFront', d.qualification_front,
|
||||
'qualificationBack', d.qualification_back,
|
||||
'qualificationValidEnd', d.qualification_end_date,
|
||||
'qualificationLongTerm', IF(d.qualification_long_term = 1, 1, 0),
|
||||
'headPhoto', d.head_photo
|
||||
),
|
||||
NOW(),
|
||||
-- 注册在司机档案上的用户 id 最直接;它为空时退回「按手机号找账号」
|
||||
-- (司机登录账号就是手机号)。两者都落空说明这名司机没有系统账号,
|
||||
-- 此时留 NULL —— 审核列表的司机列本来就取不到人,编不出来。
|
||||
IFNULL(d.user_id,
|
||||
(SELECT u.id
|
||||
FROM blade_user u
|
||||
WHERE u.account = d.mobile COLLATE utf8mb4_general_ci
|
||||
ORDER BY u.id DESC
|
||||
LIMIT 1)),
|
||||
NOW(), NOW(), 0
|
||||
FROM blade_transport_driver d
|
||||
LEFT JOIN blade_transport_cert_record r
|
||||
ON r.biz_type = 'driver' AND r.biz_id = d.id
|
||||
AND r.id >= 9100000000000000000 AND r.id < 9200000000000000000
|
||||
WHERE d.is_deleted = 0
|
||||
AND d.effective_cert_record_id IS NULL
|
||||
AND r.id IS NULL
|
||||
ORDER BY d.id;
|
||||
|
||||
UPDATE blade_transport_driver d
|
||||
JOIN blade_transport_cert_record r
|
||||
ON r.biz_type = 'driver' AND r.biz_id = d.id
|
||||
AND r.id >= 9100000000000000000 AND r.id < 9200000000000000000 AND r.status = 1
|
||||
SET d.effective_cert_record_id = r.id,
|
||||
d.certification_status = 1,
|
||||
d.certification_reject_reason = NULL
|
||||
WHERE d.effective_cert_record_id IS NULL AND d.is_deleted = 0;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- 3. 校验(执行后必看,四个都必须返回 0)
|
||||
-- ----------------------------
|
||||
-- 下列查询非 0 说明还有对象没有生效快照,门槛开着它们就不可用。
|
||||
-- SELECT COUNT(*) AS vehicle_missing FROM blade_transport_vehicle
|
||||
-- WHERE effective_cert_record_id IS NULL AND is_deleted = 0;
|
||||
-- SELECT COUNT(*) AS driver_missing FROM blade_transport_driver
|
||||
-- WHERE effective_cert_record_id IS NULL AND is_deleted = 0;
|
||||
--
|
||||
-- 下列查询非 0 说明指针指向的不是一条「已通过」记录,生效值取不到内容。
|
||||
-- SELECT COUNT(*) AS vehicle_bad_pointer
|
||||
-- FROM blade_transport_vehicle v
|
||||
-- LEFT JOIN blade_transport_cert_record r ON r.id = v.effective_cert_record_id
|
||||
-- WHERE v.effective_cert_record_id IS NOT NULL AND (r.id IS NULL OR r.status <> 1);
|
||||
-- SELECT COUNT(*) AS driver_bad_pointer
|
||||
-- FROM blade_transport_driver d
|
||||
-- LEFT JOIN blade_transport_cert_record r ON r.id = d.effective_cert_record_id
|
||||
-- WHERE d.effective_cert_record_id IS NOT NULL AND (r.id IS NULL OR r.status <> 1);
|
||||
--
|
||||
-- 快照字段完整性:确认关键字段没有因为键名写错而丢失。
|
||||
-- 司机快照必须含 driverClass(准驾车型),车辆快照必须含 drivingLicenseNo(行驶证档案编号)。
|
||||
-- SELECT COUNT(*) AS driver_snapshot_bad FROM blade_transport_cert_record
|
||||
-- WHERE biz_type = 'driver' AND JSON_EXTRACT(snapshot_json, '$.driverClass') IS NULL;
|
||||
-- SELECT COUNT(*) AS vehicle_snapshot_bad FROM blade_transport_cert_record
|
||||
-- WHERE biz_type = 'vehicle' AND JSON_EXTRACT(snapshot_json, '$.drivingLicenseNo') IS NULL;
|
||||
--
|
||||
-- 号段账目:号段内的记录数应等于本次订正的对象数(首次订正 = 车辆数 + 尚无快照的在用司机数),
|
||||
-- 指针落在号段内的对象数也应等于它。两个数对不上说明有对象漏订正。
|
||||
-- SELECT COUNT(*) AS in_range_record FROM blade_transport_cert_record
|
||||
-- WHERE id >= 9000000000000000000 AND id < 9200000000000000000;
|
||||
-- SELECT COUNT(*) AS driver_backfilled FROM blade_transport_driver
|
||||
-- WHERE effective_cert_record_id >= 9000000000000000000
|
||||
-- AND effective_cert_record_id < 9200000000000000000;
|
||||
-- SELECT COUNT(*) AS vehicle_backfilled FROM blade_transport_vehicle
|
||||
-- WHERE effective_cert_record_id >= 9000000000000000000
|
||||
-- AND effective_cert_record_id < 9200000000000000000;
|
||||
|
||||
-- ----------------------------
|
||||
-- 4. 回退
|
||||
-- ----------------------------
|
||||
-- 识别依据是 id 号段(>= 9.0e18),不是「备份表里没有的 id」:
|
||||
-- 号段法不依赖备份表是否建过,也不会误删订正之后产生的真实提交记录。
|
||||
-- 回退前先把门槛开关置 false(transport.certification.driver-gate-enabled /
|
||||
-- vehicle-gate-enabled),否则清掉指针的瞬间所有对象都不可用。
|
||||
-- 顺序:先清指针,再删记录;反了会在中间态留下悬空指针。
|
||||
-- START TRANSACTION;
|
||||
-- UPDATE blade_transport_driver
|
||||
-- SET effective_cert_record_id = NULL, certification_status = NULL,
|
||||
-- certification_reject_reason = NULL
|
||||
-- WHERE effective_cert_record_id >= 9000000000000000000
|
||||
-- AND effective_cert_record_id < 9200000000000000000;
|
||||
-- UPDATE blade_transport_vehicle
|
||||
-- SET effective_cert_record_id = NULL, certification_status = 0,
|
||||
-- certification_reject_reason = NULL
|
||||
-- WHERE effective_cert_record_id >= 9000000000000000000
|
||||
-- AND effective_cert_record_id < 9200000000000000000;
|
||||
-- DELETE FROM blade_transport_cert_record
|
||||
-- WHERE id >= 9000000000000000000 AND id < 9200000000000000000;
|
||||
-- COMMIT;
|
||||
--
|
||||
-- 车辆 certification_status 还原为 0 是近似值:订正会把原本 0 与 1 的车都写成 1,
|
||||
-- 原分布无法反推。这只影响页面上的状态标,不影响可用性(重跑订正会再次全部写成 1)。
|
||||
-- 要精确还原只能用订正前建好的 blade_transport_vehicle_certbak:
|
||||
-- UPDATE blade_transport_vehicle v
|
||||
-- JOIN blade_transport_vehicle_certbak b ON b.id = v.id
|
||||
-- SET v.certification_status = b.certification_status,
|
||||
-- v.certification_reject_reason = b.certification_reject_reason;
|
||||
Reference in New Issue
Block a user