feat(tenant): 租户-项目两级架构 Phase 2 — IoT + Ops 业务迁移

DO 迁移 (15个 TenantBaseDO → ProjectBaseDO):
- IoT: IotDeviceDO
- Ops 核心: OpsOrderDO, OpsOrderEventDO, OpsOrderDispatchDO, OpsOrderQueueDO,
  OpsBusAreaDO, OpsAreaDeviceRelationDO, OpsDeviceTrajectoryDO
- Ops 保洁: OpsOrderCleanExtDO, OpsCleanerStatusDO, OpsCleanerPerformanceMonthlyDO,
  OpsInspectionRecordDO, OpsInspectionRecordItemDO
- Ops 安保: OpsOrderSecurityExtDO, OpsAreaSecurityUserDO

IoT 适配:
- IotDeviceRespDTO 新增 projectId 字段
- IotDeviceMessage 新增 projectId 字段
- IotDeviceMessageServiceImpl.appendDeviceMessage() 设置 projectId
- IotCleanRuleMessageHandler 嵌套 ProjectUtils.execute() 设置项目上下文

缓存改造:
- ProjectRedisCacheManager extends TenantRedisCacheManager,追加 :projectId 后缀
- ViewshTenantAutoConfiguration 替换为 ProjectRedisCacheManager

SQL 迁移脚本 (sql/mysql/project/):
- 01-create-tables.sql: system_project + system_user_project 建表
- 02-default-data.sql: 默认项目 + 用户关联回填
- 03-alter-business-tables.sql: 15 张表添加 project_id (NULL → 回填 → NOT NULL → 索引)
- 04-index-audit.sql: 现有索引审计 + project_id 补充建议
- 99-rollback.sql: 完整回滚方案

附带修复:
- fix(ops): UserDispatchStatusServiceImpl 添加缺失的 KEY_PREFIX 常量

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-04-16 22:27:34 +08:00
parent 87beb1228e
commit a2f500fa20
27 changed files with 648 additions and 176 deletions

View File

@@ -0,0 +1,38 @@
-- =============================================
-- 租户-项目两级架构 - 建表脚本
-- =============================================
-- 1. 项目表
CREATE TABLE IF NOT EXISTS system_project (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '项目编号',
tenant_id BIGINT NOT NULL COMMENT '所属租户编号',
name VARCHAR(100) NOT NULL COMMENT '项目名称',
code VARCHAR(50) NOT NULL COMMENT '项目编码(如楼盘编号)',
status TINYINT DEFAULT 0 COMMENT '状态0=正常, 1=禁用)',
contact_name VARCHAR(30) COMMENT '项目联系人',
contact_mobile VARCHAR(30) COMMENT '联系手机',
address VARCHAR(500) COMMENT '项目地址',
remark VARCHAR(500) COMMENT '备注',
creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted BIT DEFAULT 0 COMMENT '是否删除',
UNIQUE KEY uk_tenant_code (tenant_id, code, deleted),
INDEX idx_tenant (tenant_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目表';
-- 2. 用户-项目关联表
CREATE TABLE IF NOT EXISTS system_user_project (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
user_id BIGINT NOT NULL COMMENT '用户编号',
project_id BIGINT NOT NULL COMMENT '项目编号',
tenant_id BIGINT NOT NULL COMMENT '租户编号',
creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted BIT DEFAULT 0 COMMENT '是否删除',
UNIQUE KEY uk_user_project (user_id, project_id, deleted),
INDEX idx_project (project_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户-项目关联表';

View File

@@ -0,0 +1,21 @@
-- =============================================
-- 租户-项目两级架构 - 默认数据迁移
-- 为每个现有租户创建默认项目,为所有用户关联默认项目
-- =============================================
-- Step 1: 为每个现有租户创建默认项目
INSERT INTO system_project (tenant_id, name, code, status, remark, creator, updater, deleted)
SELECT id, name, 'DEFAULT', 0, '系统自动生成默认项目', 'system', 'system', 0
FROM system_tenant WHERE deleted = 0;
-- Step 2: 为所有用户关联默认项目
INSERT INTO system_user_project (user_id, project_id, tenant_id, creator, updater, deleted)
SELECT u.id, p.id, u.tenant_id, 'system', 'system', 0
FROM system_users u
JOIN system_project p ON p.tenant_id = u.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
WHERE u.deleted = 0;
-- 验证
SELECT '默认项目数量' AS metric, COUNT(*) AS value FROM system_project WHERE code = 'DEFAULT' AND deleted = 0
UNION ALL
SELECT '用户-项目关联数量', COUNT(*) FROM system_user_project WHERE deleted = 0;

View File

@@ -0,0 +1,215 @@
-- =============================================
-- 租户-项目两级架构 - 业务表增加 project_id 字段
-- 执行顺序ADD NULL → UPDATE 回填 → MODIFY NOT NULL → ADD INDEX
-- 前置条件02-default-data.sql 已执行system_project 中已有默认项目数据
-- =============================================
-- -----------------------------------------------
-- 1. iot_device
-- -----------------------------------------------
ALTER TABLE iot_device ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE iot_device d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE iot_device MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE iot_device ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 2. ops_order
-- -----------------------------------------------
ALTER TABLE ops_order ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_order d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_order MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_order ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 3. ops_order_event
-- -----------------------------------------------
ALTER TABLE ops_order_event ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_order_event d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_order_event MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_order_event ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 4. ops_order_dispatch
-- -----------------------------------------------
ALTER TABLE ops_order_dispatch ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_order_dispatch d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_order_dispatch MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_order_dispatch ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 5. ops_order_queue
-- -----------------------------------------------
ALTER TABLE ops_order_queue ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_order_queue d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_order_queue MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_order_queue ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 6. ops_bus_area
-- -----------------------------------------------
ALTER TABLE ops_bus_area ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_bus_area d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_bus_area MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_bus_area ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 7. ops_area_device_relation
-- -----------------------------------------------
ALTER TABLE ops_area_device_relation ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_area_device_relation d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_area_device_relation MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_area_device_relation ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 8. ops_order_security_ext
-- -----------------------------------------------
ALTER TABLE ops_order_security_ext ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_order_security_ext d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_order_security_ext MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_order_security_ext ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 9. ops_area_security_user
-- -----------------------------------------------
ALTER TABLE ops_area_security_user ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_area_security_user d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_area_security_user MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_area_security_user ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 10. ops_order_clean_ext
-- -----------------------------------------------
ALTER TABLE ops_order_clean_ext ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_order_clean_ext d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_order_clean_ext MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_order_clean_ext ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 11. ops_cleaner_status
-- -----------------------------------------------
ALTER TABLE ops_cleaner_status ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_cleaner_status d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_cleaner_status MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_cleaner_status ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 12. ops_cleaner_performance_monthly
-- -----------------------------------------------
ALTER TABLE ops_cleaner_performance_monthly ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_cleaner_performance_monthly d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_cleaner_performance_monthly MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_cleaner_performance_monthly ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 13. ops_device_trajectory
-- -----------------------------------------------
ALTER TABLE ops_device_trajectory ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_device_trajectory d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_device_trajectory MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_device_trajectory ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 14. ops_inspection_record
-- -----------------------------------------------
ALTER TABLE ops_inspection_record ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_inspection_record d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_inspection_record MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_inspection_record ADD INDEX idx_project_id (project_id);
-- -----------------------------------------------
-- 15. ops_inspection_record_item
-- -----------------------------------------------
ALTER TABLE ops_inspection_record_item ADD COLUMN project_id BIGINT NULL COMMENT '项目编号';
UPDATE ops_inspection_record_item d
JOIN system_project p ON p.tenant_id = d.tenant_id AND p.code = 'DEFAULT' AND p.deleted = 0
SET d.project_id = p.id
WHERE d.project_id IS NULL;
ALTER TABLE ops_inspection_record_item MODIFY COLUMN project_id BIGINT NOT NULL COMMENT '项目编号';
ALTER TABLE ops_inspection_record_item ADD INDEX idx_project_id (project_id);

View File

@@ -0,0 +1,86 @@
-- =============================================
-- 租户-项目两级架构 - 唯一索引审计脚本
-- 检查各业务表现有唯一键,评估是否需要将 project_id 纳入唯一约束
-- 执行顺序:在 03-alter-business-tables.sql 之后执行
-- =============================================
-- -----------------------------------------------
-- 查看各表当前的索引情况(审计用,执行前先 SHOW INDEX 确认)
-- -----------------------------------------------
SHOW INDEX FROM iot_device;
SHOW INDEX FROM ops_order;
SHOW INDEX FROM ops_order_event;
SHOW INDEX FROM ops_order_dispatch;
SHOW INDEX FROM ops_order_queue;
SHOW INDEX FROM ops_bus_area;
SHOW INDEX FROM ops_area_device_relation;
SHOW INDEX FROM ops_order_security_ext;
SHOW INDEX FROM ops_area_security_user;
SHOW INDEX FROM ops_order_clean_ext;
SHOW INDEX FROM ops_cleaner_status;
SHOW INDEX FROM ops_cleaner_performance_monthly;
SHOW INDEX FROM ops_device_trajectory;
SHOW INDEX FROM ops_inspection_record;
SHOW INDEX FROM ops_inspection_record_item;
-- -----------------------------------------------
-- 唯一键改造建议(根据实际业务语义决定是否执行)
-- 说明:若某张表的唯一键仅含 tenant_id则需要将 project_id 也纳入,
-- 以确保在同一租户下不同项目之间的数据互不干扰。
-- -----------------------------------------------
-- ops_bus_area区域编码在项目内唯一原唯一键可能仅含 tenant_id + code
-- 示例DROP INDEX uk_tenant_code ON ops_bus_area;
-- 示例ALTER TABLE ops_bus_area ADD UNIQUE KEY uk_project_code (tenant_id, project_id, code, deleted);
-- ops_area_device_relation设备-区域关联在项目内唯一
-- 示例DROP INDEX uk_area_device ON ops_area_device_relation;
-- 示例ALTER TABLE ops_area_device_relation ADD UNIQUE KEY uk_project_area_device (project_id, area_id, device_id, deleted);
-- ops_area_security_user安保用户-区域关联在项目内唯一
-- 示例DROP INDEX uk_area_user ON ops_area_security_user;
-- 示例ALTER TABLE ops_area_security_user ADD UNIQUE KEY uk_project_area_user (project_id, area_id, user_id, deleted);
-- ops_cleaner_status保洁员状态在项目内唯一每个项目内每个用户仅一条状态记录
-- 示例DROP INDEX uk_tenant_user ON ops_cleaner_status;
-- 示例ALTER TABLE ops_cleaner_status ADD UNIQUE KEY uk_project_user (project_id, user_id, deleted);
-- ops_cleaner_performance_monthly月度绩效在项目内唯一project + user + year_month
-- 示例DROP INDEX uk_tenant_user_month ON ops_cleaner_performance_monthly;
-- 示例ALTER TABLE ops_cleaner_performance_monthly ADD UNIQUE KEY uk_project_user_month (project_id, user_id, stat_year, stat_month, deleted);
-- iot_device设备标识符在项目内唯一device_key / product_key + device_name
-- 示例:根据实际唯一键定义决定是否改造。
-- -----------------------------------------------
-- 验证:各表 project_id 回填情况
-- -----------------------------------------------
SELECT 'iot_device' AS tbl, COUNT(*) AS total, SUM(project_id IS NULL) AS null_count FROM iot_device
UNION ALL
SELECT 'ops_order', COUNT(*), SUM(project_id IS NULL) FROM ops_order
UNION ALL
SELECT 'ops_order_event', COUNT(*), SUM(project_id IS NULL) FROM ops_order_event
UNION ALL
SELECT 'ops_order_dispatch', COUNT(*), SUM(project_id IS NULL) FROM ops_order_dispatch
UNION ALL
SELECT 'ops_order_queue', COUNT(*), SUM(project_id IS NULL) FROM ops_order_queue
UNION ALL
SELECT 'ops_bus_area', COUNT(*), SUM(project_id IS NULL) FROM ops_bus_area
UNION ALL
SELECT 'ops_area_device_relation', COUNT(*), SUM(project_id IS NULL) FROM ops_area_device_relation
UNION ALL
SELECT 'ops_order_security_ext', COUNT(*), SUM(project_id IS NULL) FROM ops_order_security_ext
UNION ALL
SELECT 'ops_area_security_user', COUNT(*), SUM(project_id IS NULL) FROM ops_area_security_user
UNION ALL
SELECT 'ops_order_clean_ext', COUNT(*), SUM(project_id IS NULL) FROM ops_order_clean_ext
UNION ALL
SELECT 'ops_cleaner_status', COUNT(*), SUM(project_id IS NULL) FROM ops_cleaner_status
UNION ALL
SELECT 'ops_cleaner_performance_monthly', COUNT(*), SUM(project_id IS NULL) FROM ops_cleaner_performance_monthly
UNION ALL
SELECT 'ops_device_trajectory', COUNT(*), SUM(project_id IS NULL) FROM ops_device_trajectory
UNION ALL
SELECT 'ops_inspection_record', COUNT(*), SUM(project_id IS NULL) FROM ops_inspection_record
UNION ALL
SELECT 'ops_inspection_record_item', COUNT(*), SUM(project_id IS NULL) FROM ops_inspection_record_item;

View File

@@ -0,0 +1,46 @@
-- =============================================
-- 租户-项目两级架构 - 回滚脚本
-- 执行顺序与正向脚本相反:业务表 → 默认数据 → 建表
-- =============================================
-- -----------------------------------------------
-- Step 1: 回滚业务表(删除 project_id 字段及索引)
-- -----------------------------------------------
ALTER TABLE iot_device DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_order DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_order_event DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_order_dispatch DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_order_queue DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_bus_area DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_area_device_relation DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_order_security_ext DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_area_security_user DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_order_clean_ext DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_cleaner_status DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_cleaner_performance_monthly DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_device_trajectory DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_inspection_record DROP INDEX idx_project_id, DROP COLUMN project_id;
ALTER TABLE ops_inspection_record_item DROP INDEX idx_project_id, DROP COLUMN project_id;
-- -----------------------------------------------
-- Step 2: 回滚默认数据(删除用户-项目关联 和 默认项目)
-- -----------------------------------------------
-- 删除系统自动生成的用户-项目关联
DELETE FROM system_user_project WHERE creator = 'system' AND deleted = 0;
-- 删除系统自动生成的默认项目
DELETE FROM system_project WHERE code = 'DEFAULT' AND creator = 'system' AND deleted = 0;
-- -----------------------------------------------
-- Step 3: 删除核心表
-- -----------------------------------------------
DROP TABLE IF EXISTS system_user_project;
DROP TABLE IF EXISTS system_project;
-- -----------------------------------------------
-- 验证
-- -----------------------------------------------
SELECT '回滚完成' AS status;