This commit is contained in:
2026-08-05 16:26:10 +08:00
parent 06f7f9d2f2
commit 5ba9716e12
155 changed files with 2883 additions and 551 deletions
+72 -1
View File
@@ -9,7 +9,7 @@
- 固定入口不包含 `auth/phase1-admin-accounts.sql` 和 `warehouse/warehouse-seed.sql`,不会写入固定密码账号、演示仓库、演示快递公司或演示订单。
- `warehouse/phase2-pallet-location-schema-migration.sql` 的有效结构已被当前 `phase2-pallet-location-schema.sql` 和后续共享溢出位迁移完整覆盖,因此不重复内联,避免每次部署无条件删除并重建同名唯一索引。
- 脚本会创建 8 个独立数据库:`baiyue_auth`、`baiyue_iam`、`baiyue_order`、`baiyue_warehouse`、`baiyue_firstleg`、`baiyue_file`、`baiyue_audit`、`baiyue_event_bridge`。Gateway、Print、Integration Service 无数据库。
- 完整执行后只有关键表、字段和 28 项二期权限全部存在,末尾才会返回 `PASS`;任一强校验失败会通过 `SIGNAL` 终止。
- 完整执行所有增量区块并通过末段真实结构校验后,脚本最末才会返回唯一的 `PASS`;任一强校验失败会通过 `SIGNAL` 终止。
后续追加增量必须满足:
@@ -79,6 +79,77 @@ SELECT COUNT(*) = 8 AS scheduler_columns_ready
- 新增 `external_request_hash`、`inbound_detail_id`、`external_detail_no` 和第三方明细唯一索引,用于一条回传绑定一条订单货物明细及一张文件中心图片。
- 部署 Firstleg 新版本前必须完整执行固定入口,并确认末尾总体验证为 `PASS`。
## 当前主体字段语义核验
- 业务单租户阶段,业务表中的 `tenant_id > 0` 是客户 ID 和客户隔离键,不表示上级租户;费用项目和入库费用明细沿用该物理字段名。
- `fl_third_party_call_log.tenant_id = 0` 仅表示第三方主体未知或尚未解析的哨兵值,不是平台客户记录或平台操作作用域,绝不能据此授权、查询或写入客户业务数据。上线后可只读统计:
```sql
SELECT COUNT(*) AS unresolved_third_party_calls
FROM baiyue_firstleg.fl_third_party_call_log
WHERE tenant_id = 0;
```
- 平台公共仓库、地址、服务能力、往来单位 `wh_partner`、快递公司、托盘、库位、预占和占用固定归平台作用域 `tenant_id=0`;Firstleg 的 `warehouse_resource_tenant_id` 同步固定为 `0`。
- 客户订单、客户文件和客户费用规则仍使用 `tenant_id>0` 做客户隔离;往来单位不是客户私有数据,统一脚本会把 `wh_partner` 和 `wh_partner_type` 的作用域归零。
## 平台公共资源归零迁移与核验
测试环境仍只执行固定入口 `头程数据库统一增量-MySQL8.sql`,不要再串行执行模块脚本。执行前可只读盘点公共资源旧归属:
```sql
SELECT 'wh_warehouse' AS resource, COUNT(*) AS total, COALESCE(SUM(tenant_id <> 0), 0) AS non_platform
FROM baiyue_warehouse.wh_warehouse
UNION ALL SELECT 'wh_warehouse_address', COUNT(*), COALESCE(SUM(tenant_id <> 0), 0) FROM baiyue_warehouse.wh_warehouse_address
UNION ALL SELECT 'wh_service_capability', COUNT(*), COALESCE(SUM(tenant_id <> 0), 0) FROM baiyue_warehouse.wh_service_capability
UNION ALL SELECT 'wh_express_company', COUNT(*), COALESCE(SUM(tenant_id <> 0), 0) FROM baiyue_warehouse.wh_express_company
UNION ALL SELECT 'wh_pallet', COUNT(*), COALESCE(SUM(tenant_id <> 0), 0) FROM baiyue_warehouse.wh_pallet
UNION ALL SELECT 'wh_location', COUNT(*), COALESCE(SUM(tenant_id <> 0), 0) FROM baiyue_warehouse.wh_location
UNION ALL SELECT 'wh_location_reservation', COUNT(*), COALESCE(SUM(tenant_id <> 0), 0) FROM baiyue_warehouse.wh_location_reservation
UNION ALL SELECT 'wh_location_occupancy', COUNT(*), COALESCE(SUM(tenant_id <> 0), 0) FROM baiyue_warehouse.wh_location_occupancy
UNION ALL SELECT 'fl_inbound_pallet_allocation.resource_scope', COUNT(*),
COALESCE(SUM(warehouse_resource_tenant_id <> 0), 0)
FROM baiyue_firstleg.fl_inbound_pallet_allocation;
```
脚本会先把明确公共资源迁到 `0`,按父仓库回填地址和能力,再迁移平台往来单位、托盘、库位、预占、占用及 Firstleg 资源归属快照。末尾 `PASS` 前会强校验公共资源(含往来单位)全部归零、父子一致,以及客户费用规则不存在 `tenant_id=0`。执行后可再只读核验:
```sql
SELECT
(SELECT COUNT(*) FROM baiyue_warehouse.wh_warehouse WHERE tenant_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_warehouse_address WHERE tenant_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_service_capability WHERE tenant_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_partner
WHERE tenant_id <> 0 OR data_scope_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_partner_type
WHERE tenant_id <> 0 OR data_scope_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_express_company WHERE tenant_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_pallet WHERE tenant_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_location WHERE tenant_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_location_reservation WHERE tenant_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_location_occupancy WHERE tenant_id <> 0)
+ (SELECT COUNT(*) FROM baiyue_firstleg.fl_inbound_pallet_allocation
WHERE warehouse_resource_tenant_id <> 0) AS public_scope_violations,
(SELECT COUNT(*) FROM baiyue_warehouse.wh_overseas_storage_fee_rule WHERE tenant_id = 0)
AS customer_scope_violations;
SELECT
(SELECT COUNT(*) FROM baiyue_warehouse.wh_warehouse_address a
JOIN baiyue_warehouse.wh_warehouse w ON w.id = a.warehouse_id
WHERE a.tenant_id <> w.tenant_id)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_service_capability c
JOIN baiyue_warehouse.wh_warehouse w ON w.id = c.warehouse_id
WHERE c.tenant_id <> w.tenant_id)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_pallet p
JOIN baiyue_warehouse.wh_warehouse w ON w.id = p.warehouse_id
WHERE p.tenant_id <> w.tenant_id)
+ (SELECT COUNT(*) FROM baiyue_warehouse.wh_location l
JOIN baiyue_warehouse.wh_warehouse w ON w.id = l.warehouse_id
WHERE l.tenant_id <> w.tenant_id) AS parent_child_scope_mismatches;
```
上述三个结果 `public_scope_violations`、`customer_scope_violations`、`parent_child_scope_mismatches` 必须都为 `0`。
## 海外仓阶梯费用增量
- `2026-08-05` 增量已追加到固定入口末尾,不创建新的日期部署脚本。
+325 -39
View File
@@ -598,8 +598,8 @@ USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_warehouse (
id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓库所属租户ID',
warehouse_code VARCHAR(50) NOT NULL COMMENT '租户内唯一仓库编码',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓库固定使用 tenant_id=0',
warehouse_code VARCHAR(50) NOT NULL COMMENT '平台内唯一仓库编码',
warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称',
warehouse_type VARCHAR(32) NOT NULL DEFAULT 'DOMESTIC' COMMENT '公共仓库类型:DOMESTIC、FBA、OVERSEAS',
country_code CHAR(2) NOT NULL COMMENT '所在国家二字码',
@@ -620,11 +620,11 @@ CREATE TABLE IF NOT EXISTS wh_warehouse (
KEY idx_wh_tenant_status (tenant_id, status, deleted),
KEY idx_wh_tenant_type_status (tenant_id, warehouse_type, status, deleted),
KEY idx_wh_country (tenant_id, country_code, deleted)
) ENGINE=InnoDB COMMENT='仓库主数据表:以租户为边界维护仓库编码、名称、国家、时区和启停状态';
) ENGINE=InnoDB COMMENT='平台公共仓库主数据表:统一归属平台作用域 tenant_id=0';
CREATE TABLE IF NOT EXISTS wh_warehouse_address (
id BIGINT UNSIGNED NOT NULL COMMENT '仓库地址ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓库地址固定使用 tenant_id=0',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
address_type VARCHAR(20) NOT NULL DEFAULT 'OPERATING' COMMENT '地址类型,例如OPERATING或RETURN',
country_code CHAR(2) NOT NULL COMMENT '国家二字码',
@@ -649,7 +649,7 @@ CREATE TABLE IF NOT EXISTS wh_warehouse_address (
CREATE TABLE IF NOT EXISTS wh_service_capability (
id BIGINT UNSIGNED NOT NULL COMMENT '服务能力ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓库服务能力固定使用 tenant_id=0',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
capability_code VARCHAR(50) NOT NULL COMMENT '能力编码:RECEIVING、STORAGE、PICK_PACK、RETURN等',
capability_name VARCHAR(100) NOT NULL COMMENT '能力名称',
@@ -690,20 +690,20 @@ CREATE TABLE IF NOT EXISTS wh_partner (
deleted TINYINT(1) NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 0,
PRIMARY KEY(id), UNIQUE KEY uk_wh_partner_tenant_code(tenant_id,partner_code,deleted),
KEY idx_wh_partner_tenant_status(tenant_id,status,deleted)
) ENGINE=InnoDB COMMENT='仓储域内的租户往来单位配置';
) ENGINE=InnoDB COMMENT='平台统一维护、供全部客户共享的往来单位主数据';
CREATE TABLE IF NOT EXISTS wh_partner_type (
partner_id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL,
data_scope_id BIGINT UNSIGNED NOT NULL, partner_type VARCHAR(32) NOT NULL,
PRIMARY KEY(partner_id,partner_type),
KEY idx_wh_partner_type_scope(tenant_id,data_scope_id,partner_type,partner_id)
) ENGINE=InnoDB COMMENT='往来单位稳定业务类型';
) ENGINE=InnoDB COMMENT='平台共享往来单位稳定业务类型';
INSERT INTO wh_partner(id,tenant_id,data_scope_id,partner_code,partner_name,partner_type,contact_name,contact_phone,contact_email,country_code,province,city,district,address_line,postal_code,status)
VALUES(191000000000000001,10001,10001,'CUS-001','深圳示例贸易有限公司','CUSTOMER','张三','13800138000','contact@example.com','CN','广东省','深圳市','宝安区','航城街道示例路1号','518000','ACTIVE')
ON DUPLICATE KEY UPDATE partner_name=VALUES(partner_name),status='ACTIVE';
VALUES(191000000000000001,0,0,'CUS-001','深圳示例贸易有限公司','CUSTOMER','张三','13800138000','contact@example.com','CN','广东省','深圳市','宝安区','航城街道示例路1号','518000','ACTIVE')
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id),data_scope_id=VALUES(data_scope_id),partner_name=VALUES(partner_name),status='ACTIVE';
INSERT INTO wh_partner_type(partner_id,tenant_id,data_scope_id,partner_type)
VALUES
(191000000000000001,10001,10001,'CUSTOMER'),
(191000000000000001,10001,10001,'SHIPPER')
(191000000000000001,0,0,'CUSTOMER'),
(191000000000000001,0,0,'SHIPPER')
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id),data_scope_id=VALUES(data_scope_id);
-- ============================================================================
@@ -714,8 +714,8 @@ USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_express_company (
id BIGINT UNSIGNED NOT NULL COMMENT '快递公司记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '所属租户ID',
company_code VARCHAR(50) NOT NULL COMMENT '租户内唯一快递公司编码',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共快递公司固定使用 tenant_id=0',
company_code VARCHAR(50) NOT NULL COMMENT '平台内唯一快递公司编码',
company_name VARCHAR(128) NOT NULL COMMENT '快递公司名称',
service_phone VARCHAR(32) DEFAULT NULL COMMENT '客服电话',
website VARCHAR(255) DEFAULT NULL COMMENT '官方网站',
@@ -732,7 +732,7 @@ CREATE TABLE IF NOT EXISTS wh_express_company (
UNIQUE KEY uk_wh_express_tenant_code (tenant_id, company_code, deleted),
UNIQUE KEY uk_wh_express_tenant_name (tenant_id, company_name, deleted),
KEY idx_wh_express_tenant_status (tenant_id, status, deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='租户维度快递公司主数据';
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台公共快递公司主数据,固定归属 tenant_id=0';
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\firstleg.sql
@@ -1709,6 +1709,54 @@ CALL add_column_if_missing('wh_express_company', 'updated_at', 'DATETIME(3) NOT
CALL add_column_if_missing('wh_express_company', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_express_company', 'version', 'INT NOT NULL DEFAULT 0');
-- 平台公共仓储主数据和平台往来单位统一归平台作用域 0;订单自身仍按客户 tenant_id 隔离。
UPDATE wh_warehouse SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_warehouse_address a
JOIN wh_warehouse w ON w.id = a.warehouse_id
SET a.tenant_id = w.tenant_id
WHERE a.tenant_id <> w.tenant_id;
UPDATE wh_service_capability c
JOIN wh_warehouse w ON w.id = c.warehouse_id
SET c.tenant_id = w.tenant_id
WHERE c.tenant_id <> w.tenant_id;
UPDATE wh_warehouse_address SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_service_capability SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_express_company SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_partner SET tenant_id = 0, data_scope_id = 0
WHERE tenant_id <> 0 OR data_scope_id <> 0;
UPDATE wh_partner_type SET tenant_id = 0, data_scope_id = 0
WHERE tenant_id <> 0 OR data_scope_id <> 0;
DROP PROCEDURE IF EXISTS assert_platform_public_resource_scope;
DELIMITER $$
CREATE PROCEDURE assert_platform_public_resource_scope()
BEGIN
IF (SELECT COUNT(*) FROM wh_warehouse WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_warehouse_address WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_service_capability WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_express_company WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_partner WHERE tenant_id <> 0 OR data_scope_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_partner_type WHERE tenant_id <> 0 OR data_scope_id <> 0) > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 迁移失败:平台公共仓储或往来单位仍有非0作用域';
END IF;
IF EXISTS (
SELECT 1 FROM wh_warehouse_address a
JOIN wh_warehouse w ON w.id = a.warehouse_id
WHERE a.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM wh_service_capability c
JOIN wh_warehouse w ON w.id = c.warehouse_id
WHERE c.tenant_id <> w.tenant_id
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 迁移失败:仓库子表与父仓库平台归属不一致';
END IF;
END$$
DELIMITER ;
CALL assert_platform_public_resource_scope();
DROP PROCEDURE IF EXISTS assert_platform_public_resource_scope;
DROP PROCEDURE IF EXISTS add_column_if_missing;
-- MySQL 8 的 ALTER ADD INDEX 不在所有小版本都支持 IF NOT EXISTS,使用元数据判断以保证重复执行安全。
@@ -1774,9 +1822,9 @@ USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_pallet (
id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共托盘固定使用 tenant_id=0',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
pallet_code VARCHAR(50) NOT NULL COMMENT '租户仓库内唯一托盘编码',
pallet_code VARCHAR(50) NOT NULL COMMENT '平台仓库内唯一托盘编码',
pallet_type VARCHAR(32) NOT NULL COMMENT '托盘类型',
pallet_name VARCHAR(100) NULL COMMENT '托盘名称',
capacity_cbm DECIMAL(18,6) NOT NULL COMMENT '托盘可用容积CBM',
@@ -1798,11 +1846,11 @@ CREATE TABLE IF NOT EXISTS wh_pallet (
CREATE TABLE IF NOT EXISTS wh_location (
id BIGINT UNSIGNED NOT NULL COMMENT '库位ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共库位固定使用 tenant_id=0',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
zone_code VARCHAR(50) NULL COMMENT '库区编码',
zone_name VARCHAR(100) NULL COMMENT '库区名称',
location_code VARCHAR(50) NOT NULL COMMENT '租户仓库内唯一库位编码',
location_code VARCHAR(50) NOT NULL COMMENT '平台仓库内唯一库位编码',
location_name VARCHAR(100) NULL COMMENT '库位名称',
location_type VARCHAR(32) NOT NULL DEFAULT 'PALLET' COMMENT '库位类型,二期为PALLET',
max_weight_kg DECIMAL(18,3) NULL COMMENT '最大承重展示值,二期不作容量拦截',
@@ -1825,8 +1873,8 @@ CREATE TABLE IF NOT EXISTS wh_location (
CREATE TABLE IF NOT EXISTS wh_location_reservation (
id BIGINT UNSIGNED NOT NULL COMMENT '库位预占ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
reservation_no VARCHAR(40) NOT NULL COMMENT '租户内唯一预占业务号',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共库位预占固定使用 tenant_id=0',
reservation_no VARCHAR(40) NOT NULL COMMENT '平台内唯一预占业务号',
idempotency_key VARCHAR(128) NOT NULL COMMENT '调用方幂等键',
source_service VARCHAR(40) NOT NULL DEFAULT 'FIRSTLEG' COMMENT '来源服务',
business_id BIGINT UNSIGNED NOT NULL COMMENT '来源服务入库单ID',
@@ -1863,8 +1911,8 @@ CREATE TABLE IF NOT EXISTS wh_location_reservation (
CREATE TABLE IF NOT EXISTS wh_location_occupancy (
id BIGINT UNSIGNED NOT NULL COMMENT '库位占用ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
occupancy_no VARCHAR(40) NOT NULL COMMENT '租户内唯一占用业务号',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共库位占用固定使用 tenant_id=0',
occupancy_no VARCHAR(40) NOT NULL COMMENT '平台内唯一占用业务号',
reservation_id BIGINT UNSIGNED NOT NULL COMMENT '来源预占ID',
source_service VARCHAR(40) NOT NULL DEFAULT 'FIRSTLEG' COMMENT '来源服务',
business_id BIGINT UNSIGNED NOT NULL COMMENT '来源服务入库单ID',
@@ -2058,6 +2106,52 @@ SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_sc
'ALTER TABLE wh_location_occupancy ADD UNIQUE KEY uk_pallet_active_occupancy (tenant_id, pallet_id, active_pallet_occupancy)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
-- 托盘、库位、预占和占用均为平台公共仓储资源,存量测试数据统一迁移到平台作用域 0。
UPDATE wh_pallet SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_location SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_location_reservation SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_location_occupancy SET tenant_id = 0 WHERE tenant_id <> 0;
DROP PROCEDURE IF EXISTS assert_phase2_public_resource_scope;
DELIMITER $$
CREATE PROCEDURE assert_phase2_public_resource_scope()
BEGIN
IF (SELECT COUNT(*) FROM wh_pallet WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_location WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_location_reservation WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_location_occupancy WHERE tenant_id <> 0) > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 二期迁移失败:托盘、库位、预占或占用仍有 tenant_id<>0';
END IF;
IF EXISTS (
SELECT 1 FROM wh_pallet p JOIN wh_warehouse w ON w.id = p.warehouse_id
WHERE p.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM wh_location l JOIN wh_warehouse w ON w.id = l.warehouse_id
WHERE l.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM wh_location_reservation r
JOIN wh_warehouse w ON w.id = r.warehouse_id
JOIN wh_pallet p ON p.id = r.pallet_id
JOIN wh_location l ON l.id = r.location_id
WHERE r.tenant_id <> w.tenant_id OR r.tenant_id <> p.tenant_id OR r.tenant_id <> l.tenant_id
) OR EXISTS (
SELECT 1 FROM wh_location_occupancy o
JOIN wh_warehouse w ON w.id = o.warehouse_id
JOIN wh_pallet p ON p.id = o.pallet_id
JOIN wh_location l ON l.id = o.location_id
JOIN wh_location_reservation r ON r.id = o.reservation_id
WHERE o.tenant_id <> w.tenant_id OR o.tenant_id <> p.tenant_id
OR o.tenant_id <> l.tenant_id OR o.tenant_id <> r.tenant_id
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 二期迁移失败:公共仓储资源父子归属不一致';
END IF;
END$$
DELIMITER ;
CALL assert_phase2_public_resource_scope();
DROP PROCEDURE IF EXISTS assert_phase2_public_resource_scope;
-- ============================================================================
-- 来源:order\src\main\resources\db\phase2-order-carrier-migration.sql
-- ============================================================================
@@ -2514,7 +2608,7 @@ CREATE TABLE IF NOT EXISTS fl_inbound_pallet_allocation (
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
allocation_no INT UNSIGNED NOT NULL COMMENT '入库单内分配序号',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务仓库ID',
warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓储资源所属租户ID',
warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键',
pallet_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务托盘ID',
pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照',
pallet_capacity_cbm DECIMAL(18,4) NOT NULL COMMENT '托盘可用容积快照',
@@ -2777,7 +2871,7 @@ USE baiyue_firstleg;
-- 既有二期数据库增量:先确保 phase2-inbound-schema.sql 中的测量和幂等表已存在。
CREATE TABLE IF NOT EXISTS fl_third_party_call_log (
id BIGINT UNSIGNED NOT NULL COMMENT '第三方调用日志ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '识别出的租户ID,未知应用为0',
tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权',
app_key VARCHAR(128) NULL COMMENT '第三方应用标识',
signature_subject VARCHAR(128) NULL COMMENT '签名主体快照',
request_method VARCHAR(8) NOT NULL COMMENT 'HTTP方法',
@@ -2802,6 +2896,17 @@ CREATE TABLE IF NOT EXISTS fl_third_party_call_log (
UNIQUE KEY uk_third_call_idempotency_owner (tenant_id, app_key, idempotency_key, idempotency_anchor)
) ENGINE=InnoDB COMMENT='第三方测量开放接口原始请求、签名主体和处理结果留档';
-- 既有表也要同步未知主体 0 的语义,避免历史注释继续误导为真实租户。
SET @third_party_tenant_comment = '第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权';
SET @ddl = IF(
(SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_third_party_call_log'
AND column_name = 'tenant_id' AND column_comment = @third_party_tenant_comment) = 1,
'SELECT 1',
'ALTER TABLE fl_third_party_call_log MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权'''
);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase2-third-party-single-measurement-migration.sql
-- ============================================================================
@@ -2898,17 +3003,18 @@ CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_from', "DATETIME(3)
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_to', "DATETIME(3) NULL COMMENT '价格生效结束快照' AFTER rate_effective_from");
CALL phase2_fba_add_column('fl_inbound_idempotency', 'result_resource_id', "BIGINT UNSIGNED NULL COMMENT '成功结果资源ID' AFTER result_json");
CALL phase2_fba_add_column('fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id',
"BIGINT UNSIGNED NULL COMMENT '仓储资源所属租户ID' AFTER warehouse_id");
"BIGINT UNSIGNED NULL COMMENT '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键' AFTER warehouse_id");
-- 旧分配记录创建于共享仓储资源租户拆分前,按原业务租户回填后再收口非空约束。
-- 仓储资源是平台公共主数据;存量分配快照统一迁移到平台作用域 0,再收口非空约束。
UPDATE fl_inbound_pallet_allocation
SET warehouse_resource_tenant_id = tenant_id
WHERE warehouse_resource_tenant_id IS NULL OR warehouse_resource_tenant_id = 0;
SET @resource_tenant_nullable = (SELECT IS_NULLABLE FROM information_schema.COLUMNS
SET warehouse_resource_tenant_id = 0
WHERE warehouse_resource_tenant_id IS NULL OR warehouse_resource_tenant_id <> 0;
SET @resource_tenant_needs_modify = (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'fl_inbound_pallet_allocation'
AND COLUMN_NAME = 'warehouse_resource_tenant_id');
SET @sql = IF(@resource_tenant_nullable = 'YES',
"ALTER TABLE fl_inbound_pallet_allocation MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓储资源所属租户ID'",
AND COLUMN_NAME = 'warehouse_resource_tenant_id'
AND (IS_NULLABLE = 'YES' OR COLUMN_COMMENT <> '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键'));
SET @sql = IF(@resource_tenant_needs_modify > 0,
"ALTER TABLE fl_inbound_pallet_allocation MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键'",
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
@@ -3460,8 +3566,7 @@ DELIMITER ;
CALL unified_assert_overseas_storage_fee();
DROP PROCEDURE IF EXISTS unified_assert_overseas_storage_fee;
SELECT 'PASS' AS migration_status,
'统一增量执行完成,海外仓阶梯费用表、索引和权限校验通过' AS message;
-- 海外仓阶梯费用结构校验已完成;最终 PASS 统一在脚本末尾输出。
-- ============================================================
-- 二期:费用项目主数据与入库单应收应付费用明细
@@ -3470,7 +3575,7 @@ USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_fee_item (
id BIGINT UNSIGNED NOT NULL COMMENT '费用项目ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '账号ID,沿用tenant_id字段名',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户',
category VARCHAR(64) NOT NULL COMMENT '费用分类',
trade_scope VARCHAR(20) NOT NULL COMMENT 'DOMESTIC国内、INTERNATIONAL国际',
fee_type_name VARCHAR(128) NOT NULL COMMENT '费用类型名称',
@@ -3496,7 +3601,7 @@ CREATE TABLE IF NOT EXISTS fl_fee_item (
CREATE TABLE IF NOT EXISTS fl_inbound_fee_detail (
id BIGINT UNSIGNED NOT NULL COMMENT '入库费用明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '账号ID,沿用tenant_id字段名',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
fee_item_id BIGINT UNSIGNED NOT NULL COMMENT '绑定费用项目ID',
inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照',
@@ -3538,6 +3643,26 @@ CREATE TABLE IF NOT EXISTS fl_inbound_fee_detail (
CONSTRAINT fk_inbound_fee_detail_item FOREIGN KEY (fee_item_id) REFERENCES fl_fee_item (id)
) ENGINE=InnoDB COMMENT='入库单应收应付费用明细';
-- 既有表同步字段注释:tenant_id 当前承载客户 ID,不是上级租户 ID。
SET @fee_tenant_comment = '当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户';
SET @ddl = IF(
(SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_fee_item'
AND column_name = 'tenant_id' AND column_comment = @fee_tenant_comment) = 1,
'SELECT 1',
'ALTER TABLE fl_fee_item MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT ''当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户'''
);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @ddl = IF(
(SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_fee_detail'
AND column_name = 'tenant_id' AND column_comment = @fee_tenant_comment) = 1,
'SELECT 1',
'ALTER TABLE fl_inbound_fee_detail MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT ''当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户'''
);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
USE baiyue_iam;
INSERT INTO iam_permission
@@ -3571,8 +3696,7 @@ FROM iam_permission
WHERE id IN (5102070, 5102071, 5102072, 5102073, 5102081, 5102082, 5102083, 5102084)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
SELECT 'PASS' AS migration_status,
'费用项目、入库费用明细及按钮权限增量已执行' AS message;
-- 费用项目、入库费用明细及按钮权限结构校验延后至脚本末尾统一执行。
-- ============================================================
-- 可靠审计:本地 Outbox、RocketMQ 消费幂等和死信补偿
@@ -3637,4 +3761,166 @@ CREATE TABLE IF NOT EXISTS sys_audit_dead_letter (
KEY idx_audit_dead_retry(status,next_retry_at,lease_until)
) ENGINE=InnoDB COMMENT='审计消息死信补偿表';
SELECT 'PASS' AS migration_status,'可靠审计Outbox、幂等和死信补偿表已就绪' AS message;
-- ============================================================
-- 全部增量完成后的最终结构强校验;只有通过此段才输出唯一 PASS
-- ============================================================
DROP PROCEDURE IF EXISTS unified_assert_final_extensions;
DELIMITER $$
CREATE PROCEDURE unified_assert_final_extensions()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg'
AND table_name IN ('fl_fee_item', 'fl_inbound_fee_detail')) <> 2 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:费用项目或入库费用明细表未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name IN ('fl_fee_item', 'fl_inbound_fee_detail')
AND column_name = 'tenant_id'
AND column_comment = '当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户') <> 2 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:费用表 tenant_id 客户隔离语义未同步';
END IF;
IF (SELECT COUNT(DISTINCT CONCAT(table_name, '.', index_name))
FROM information_schema.statistics
WHERE table_schema = 'baiyue_firstleg'
AND index_name IN ('idx_fee_item_query', 'idx_fee_item_name',
'idx_inbound_fee_detail', 'idx_inbound_fee_review',
'idx_inbound_fee_item')) <> 5 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:费用项目或入库费用明细索引未完整创建';
END IF;
IF (SELECT COUNT(DISTINCT permission_code) FROM baiyue_iam.iam_permission
WHERE tenant_id = 0 AND status = 'ACTIVE' AND deleted = 0
AND permission_code IN (
'firstleg:fee-item:view', 'firstleg:fee-item:create',
'firstleg:fee-item:edit', 'firstleg:fee-item:delete',
'firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit',
'firstleg:inbound:fee-detail:delete', 'firstleg:inbound:fee-detail:review')) <> 8 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:费用项目或入库费用明细权限未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema IN ('baiyue_auth', 'baiyue_iam', 'baiyue_order',
'baiyue_firstleg', 'baiyue_warehouse')
AND table_name = 'sys_audit_outbox') <> 5 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:可靠审计 Outbox 未覆盖五个业务库';
END IF;
IF (SELECT COUNT(DISTINCT CONCAT(table_schema, '.', index_name))
FROM information_schema.statistics
WHERE table_schema IN ('baiyue_auth', 'baiyue_iam', 'baiyue_order',
'baiyue_firstleg', 'baiyue_warehouse')
AND table_name = 'sys_audit_outbox'
AND index_name IN ('PRIMARY', 'idx_audit_outbox_publish',
'idx_audit_outbox_retention')) <> 15 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:可靠审计 Outbox 主键或索引未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema IN ('baiyue_auth', 'baiyue_iam', 'baiyue_order',
'baiyue_firstleg', 'baiyue_warehouse')
AND table_name = 'sys_audit_outbox'
AND column_name IN ('event_id', 'payload_json', 'status',
'next_retry_at', 'lease_until')) <> 25 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:可靠审计 Outbox 关键字段未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'baiyue_audit'
AND table_name = 'sys_audit_dead_letter') <> 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:审计死信补偿表未创建';
END IF;
IF (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
WHERE table_schema = 'baiyue_audit'
AND table_name = 'sys_audit_dead_letter'
AND index_name IN ('PRIMARY', 'uk_audit_dead_event', 'idx_audit_dead_retry')) <> 3 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:审计死信补偿表主键或索引未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_audit' AND table_name = 'sys_audit_log'
AND column_name = 'event_id') <> 1
OR (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
WHERE table_schema = 'baiyue_audit' AND table_name = 'sys_audit_log'
AND index_name = 'uk_audit_event_id' AND non_unique = 0) <> 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:审计消费幂等 event_id 唯一约束未就绪';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_third_party_call_log'
AND column_name = 'tenant_id' AND column_default = '0'
AND column_comment = '第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权') <> 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:第三方未知主体 tenant_id=0 哨兵语义未就绪';
END IF;
IF (SELECT COUNT(*) FROM baiyue_warehouse.wh_warehouse WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_warehouse_address WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_service_capability WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_express_company WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_partner
WHERE tenant_id <> 0 OR data_scope_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_partner_type
WHERE tenant_id <> 0 OR data_scope_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_pallet WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_location WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_location_reservation WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_warehouse.wh_location_occupancy WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM baiyue_firstleg.fl_inbound_pallet_allocation
WHERE warehouse_resource_tenant_id <> 0) > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:平台公共仓储资源仍有 tenant_id<>0';
END IF;
IF EXISTS (
SELECT 1 FROM baiyue_warehouse.wh_warehouse_address a
JOIN baiyue_warehouse.wh_warehouse w ON w.id = a.warehouse_id
WHERE a.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM baiyue_warehouse.wh_service_capability c
JOIN baiyue_warehouse.wh_warehouse w ON w.id = c.warehouse_id
WHERE c.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM baiyue_warehouse.wh_pallet p
JOIN baiyue_warehouse.wh_warehouse w ON w.id = p.warehouse_id
WHERE p.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM baiyue_warehouse.wh_location l
JOIN baiyue_warehouse.wh_warehouse w ON w.id = l.warehouse_id
WHERE l.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM baiyue_warehouse.wh_location_reservation r
JOIN baiyue_warehouse.wh_warehouse w ON w.id = r.warehouse_id
JOIN baiyue_warehouse.wh_pallet p ON p.id = r.pallet_id
JOIN baiyue_warehouse.wh_location l ON l.id = r.location_id
WHERE r.tenant_id <> w.tenant_id OR r.tenant_id <> p.tenant_id OR r.tenant_id <> l.tenant_id
) OR EXISTS (
SELECT 1 FROM baiyue_warehouse.wh_location_occupancy o
JOIN baiyue_warehouse.wh_warehouse w ON w.id = o.warehouse_id
JOIN baiyue_warehouse.wh_pallet p ON p.id = o.pallet_id
JOIN baiyue_warehouse.wh_location l ON l.id = o.location_id
JOIN baiyue_warehouse.wh_location_reservation r ON r.id = o.reservation_id
WHERE o.tenant_id <> w.tenant_id OR o.tenant_id <> p.tenant_id
OR o.tenant_id <> l.tenant_id OR o.tenant_id <> r.tenant_id
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:Warehouse 公共资源父子归属不一致';
END IF;
IF EXISTS (SELECT 1 FROM baiyue_warehouse.wh_overseas_storage_fee_rule WHERE tenant_id = 0) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:客户费用规则缺少客户 tenant_id';
END IF;
END$$
DELIMITER ;
CALL unified_assert_final_extensions();
DROP PROCEDURE IF EXISTS unified_assert_final_extensions;
-- tenant_id=0 在此表仅为未知/未解析第三方主体;数量可非零,但不得用于业务授权。
SELECT COUNT(*) AS unresolved_third_party_calls
FROM baiyue_firstleg.fl_third_party_call_log
WHERE tenant_id = 0;
SELECT 'PASS' AS migration_status,
'统一增量全部执行完成,平台公共资源归0、Warehouse父子回填、费用及可靠审计结构校验通过' AS message;