fix(E): 修复 iot-device 启动失败 + V22 迁移脚本重写为可执行版本
【生产故障与修复】
iot-device 部署后反复重启失败(status=1/FAILURE, restart counter 3+):
UnsatisfiedDependencyException: No qualifying bean of type
'com.iot.device.mapper.ProductActionMapper' available
根因: iot-device 模块【没有 @MapperScan】(仅 iot-rule 有), Mapper 靠
@Mapper 注解被 mybatis-spring-boot-starter 自动扫描;
新建的 ProductEventMapper / ProductActionMapper 漏写 @Mapper 注解,
导致扫描不到 -> ControlSchemaServiceImpl 注入失败 -> 启动失败。
修复: 两个 Mapper 补 @Mapper 注解; 已全量核查 iot-device 31 个 Mapper 均有该注解。
【V22 迁移脚本重写】(实测发现原写法在本环境根本执行不了)
- 原写法使用存储过程 + DELIMITER, 实测三种执行方式(管道/文件重定向/source)
全部失败: MySQL 客户端在 batch 模式会把 DELIMITER 当普通 SQL 发给服务器。
生产库现存存储过程数为 0, 佐证历史 V19/V21 等含 DELIMITER 的脚本
其存储过程部分从未生效(仅 ADD COLUMN 等语句生效)。
- 重写为零存储过程、零 DELIMITER 的普通语句序列:
加列幂等 -> PREPARE + information_schema 判定(ADD COLUMN 走 PREPARE 实测可用)
索引 -> 直接 DDL(ADD INDEX 走 PREPARE 实测报 1064, 直接执行可用)
- 库名统一用 DATABASE() 判定, 测试库克隆无需改写脚本
- 清理顺序调整: 唯一索引统一放在软删重复行清理之后, 避免索引创建失败
【生产验证】(R730 192.168.3.3)
- 全库备份: /opt/iotsaas/backup/iot_platform_before_v22.sql
- 克隆库演练: 建克隆库 -> 跑 V22 -> 校验 -> DROP, 生产数据零风险
- 正式执行: platform_type 8行 / product.protocol / product_property.grp,
is_primary, enum_label / product_action.completion_mode 全部就位
- 唯一索引: uk_platform_product / uk_product_code(x3) / idx_product_id 全部就位
- 数据完整性: product=11 device=13, 与迁移前一致
- 数据清洗: 脏 model/manufacturer=0, 空串 platform_product_id=0, device.miot=0
- tracking 表已标记 V22, 避免 deploy 脚本重复执行
【部署脚本加固】
deploy-all.bat 迁移步骤加 --force: 迁移脚本中'已存在'的 DDL(重复列/索引)
属正常情况, 不应中断整体部署
验证: 4 服务全部 active; 后台首页/SPA路由/9081文档/9083文档 均 200;
产品列表 API 返回 401 未认证(路由与过滤器正常)
This commit is contained in:
parent
297206368e
commit
6e3f3ea661
@ -3,8 +3,10 @@ package com.iot.device.mapper;
|
|||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.iot.device.entity.ProductAction;
|
import com.iot.device.entity.ProductAction;
|
||||||
import org.apache.ibatis.annotations.Delete;
|
import org.apache.ibatis.annotations.Delete;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
public interface ProductActionMapper extends BaseMapper<ProductAction> {
|
public interface ProductActionMapper extends BaseMapper<ProductAction> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package com.iot.device.mapper;
|
|||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.iot.device.entity.ProductEvent;
|
import com.iot.device.entity.ProductEvent;
|
||||||
import org.apache.ibatis.annotations.Delete;
|
import org.apache.ibatis.annotations.Delete;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
public interface ProductEventMapper extends BaseMapper<ProductEvent> {
|
public interface ProductEventMapper extends BaseMapper<ProductEvent> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,73 +1,25 @@
|
|||||||
-- =====================================================================
|
-- =====================================================================
|
||||||
-- V22 · 产品 / 物模型 / 设备 三层架构重构(地基)
|
-- V22 · 产品 / 物模型 / 设备 三层架构重构(地基)
|
||||||
-- ---------------------------------------------------------------------
|
-- ---------------------------------------------------------------------
|
||||||
-- 背景(重构前的实测断点):
|
-- 执行特性(重要,实测结论):
|
||||||
-- 1) platform 取值域四套并存:product(tuya/local/matter) /
|
-- 本脚本【不使用存储过程、不使用 DELIMITER】,全部是普通语句序列。
|
||||||
-- device(tuya/miot/dueros/local/matter) / DevicePlatform 枚举 /
|
-- 原因:MySQL 客户端在 batch 模式(管道 / 文件重定向 / source)下
|
||||||
-- platform_config(tuya/aliyun/xfyun),miot、dueros 设备无处归类;
|
-- 会把 DELIMITER 当作普通 SQL 发给服务器,导致含存储过程的脚本
|
||||||
-- 2) 物模型两套并存:product_property(唯一被消费)与 device.schema_json
|
-- 100% 执行失败(已在生产库克隆上实测复现)。生产库现存存储过程数为 0,
|
||||||
-- (中控屏上报后只写不读,属死数据);
|
-- 即历史 V19/V21 等含 DELIMITER 的脚本其存储过程部分从未生效。
|
||||||
-- 3) product_property 无 (product_id, code) 唯一约束,saveSpec 全量替换
|
-- 幂等性改由 PREPARE + information_schema 判定实现,语义等价。
|
||||||
-- / saveProperty 增量 / reconcile-spec 物理删三条写路径语义不一;
|
-- 库名一律用 DATABASE() 判定,测试库克隆无需改写脚本。
|
||||||
-- 4) product.model / manufacturer 被涂鸦 productId 污染
|
-- ---------------------------------------------------------------------
|
||||||
-- (resolveProductId 调用方参数错位),product_key 为随机 UUID 不可读;
|
-- 本次动作(仅结构与数据清洗;业务语义回填由后端 SpecSemanticResolver
|
||||||
-- 5) device.product_id 无索引,而 OTA 按 product_id 物化目标设备 -> 全表扫;
|
-- 单一权威实现,保证规则只有一份):
|
||||||
-- 6) V19 已建 product_property.semantic/channel/completionMode 三列,
|
|
||||||
-- 但 Entity / DTO 全无对应字段 -> 语义字段整链断裂。
|
|
||||||
--
|
|
||||||
-- 本次动作(仅结构与数据清洗,不做业务语义回填;语义回填由后端
|
|
||||||
-- SpecSemanticResolver 单一权威实现,保证规则只有一份):
|
|
||||||
-- A. 新建 platform_type 平台字典表(后台可维护,取代硬编码枚举)
|
-- A. 新建 platform_type 平台字典表(后台可维护,取代硬编码枚举)
|
||||||
-- B. product:加 protocol 列;空串 platform_product_id -> NULL;
|
-- B. product:加 protocol 列;platform_product_id 空串清洗为 NULL;
|
||||||
-- 清洗 model/manufacturer 脏数据;加 (platform, platform_product_id) 唯一约束
|
-- 清洗 model/manufacturer 脏数据;加平台唯一约束
|
||||||
-- C. product_property:加 grp / is_primary / enum_label;
|
-- C. product_property:加 grp / is_primary / enum_label;加功能点唯一约束
|
||||||
-- 加 (product_id, code) 唯一约束
|
-- D. product_event / product_action:加功能点唯一约束;action 加 completion_mode
|
||||||
-- D. product_event / product_action:加 (product_id, code) 唯一约束;
|
|
||||||
-- product_action 加 completion_mode
|
|
||||||
-- E. device:加 product_id 索引;platform 归一(legacy 别名 + 空值兜底)
|
-- E. device:加 product_id 索引;platform 归一(legacy 别名 + 空值兜底)
|
||||||
--
|
|
||||||
-- 幂等说明:本脚本可重复执行(加列/加索引均由存储过程判存在性)。
|
|
||||||
-- 唯一约束前有重复检测,有重复时 ALTER 会直接报错中止,交人工合并
|
|
||||||
-- (不静默合并,避免设备错挂到错误产品)。
|
|
||||||
-- 执行方式:手工 mysql 客户端执行(本环境 Flyway 未启用,与 V3~V21 一致)。
|
|
||||||
-- =====================================================================
|
-- =====================================================================
|
||||||
|
|
||||||
-- ---------- 0) 幂等辅助过程 ----------
|
|
||||||
DROP PROCEDURE IF EXISTS v22_add_col$$
|
|
||||||
CREATE PROCEDURE v22_add_col(IN tbl VARCHAR(64), IN col VARCHAR(64), IN def TEXT)
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = 'iot_platform' AND TABLE_NAME = tbl AND COLUMN_NAME = col
|
|
||||||
) THEN
|
|
||||||
SET @s = CONCAT('ALTER TABLE ', tbl, ' ADD COLUMN ', col, ' ', def);
|
|
||||||
PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;
|
|
||||||
END IF;
|
|
||||||
END$$
|
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS v22_add_idx$$
|
|
||||||
CREATE PROCEDURE v22_add_idx(IN tbl VARCHAR(64), IN idx VARCHAR(64), IN cols VARCHAR(255))
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.STATISTICS
|
|
||||||
WHERE TABLE_SCHEMA = 'iot_platform' AND TABLE_NAME = tbl AND INDEX_NAME = idx
|
|
||||||
) THEN
|
|
||||||
SET @s = CONCAT('ALTER TABLE ', tbl, ' ADD INDEX ', idx, ' (', cols, ')');
|
|
||||||
PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;
|
|
||||||
END IF;
|
|
||||||
END$$
|
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS v22_add_uniq$$
|
|
||||||
CREATE PROCEDURE v22_add_uniq(IN tbl VARCHAR(64), IN idx VARCHAR(64), IN cols VARCHAR(255))
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM information_schema.STATISTICS
|
|
||||||
WHERE TABLE_SCHEMA = 'iot_platform' AND TABLE_NAME = tbl AND INDEX_NAME = idx
|
|
||||||
) THEN
|
|
||||||
SET @s = CONCAT('ALTER TABLE ', tbl, ' ADD UNIQUE INDEX ', idx, ' (', cols, ')');
|
|
||||||
PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;
|
|
||||||
END IF;
|
|
||||||
END$$
|
|
||||||
-- ---------- 1) 平台字典表 ----------
|
-- ---------- 1) 平台字典表 ----------
|
||||||
CREATE TABLE IF NOT EXISTS platform_type (
|
CREATE TABLE IF NOT EXISTS platform_type (
|
||||||
id BIGINT NOT NULL COMMENT '主键',
|
id BIGINT NOT NULL COMMENT '主键',
|
||||||
@ -100,108 +52,86 @@ VALUES
|
|||||||
(2260000000000000007, 'alexa', 'Amazon Alexa', 'alexa', 'wifi', 0, 70, '待接入', NOW(), NOW(), 0),
|
(2260000000000000007, 'alexa', 'Amazon Alexa', 'alexa', 'wifi', 0, 70, '待接入', NOW(), NOW(), 0),
|
||||||
(2260000000000000008, 'google_home', 'Google Home', 'google_home', 'wifi', 0, 80, '待接入', NOW(), NOW(), 0)
|
(2260000000000000008, 'google_home', 'Google Home', 'google_home', 'wifi', 0, 80, '待接入', NOW(), NOW(), 0)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
name = VALUES(name),
|
name = VALUES(name), legacy_codes = VALUES(legacy_codes), protocol = VALUES(protocol),
|
||||||
legacy_codes = VALUES(legacy_codes),
|
sort = VALUES(sort), remark = VALUES(remark), update_time = NOW();
|
||||||
protocol = VALUES(protocol),
|
|
||||||
sort = VALUES(sort),
|
|
||||||
remark = VALUES(remark),
|
|
||||||
update_time = NOW();
|
|
||||||
|
|
||||||
-- ---------- 2) product ----------
|
-- ---------- 2) product ----------
|
||||||
-- 协议维度(与 platform 正交):wifi / ble / zigbee / matter / mesh
|
-- 2.1 协议列(与 platform 正交):wifi / ble / zigbee / matter / mesh
|
||||||
CALL v22_add_col('product', 'protocol',
|
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||||
'VARCHAR(32) DEFAULT NULL COMMENT ''联网协议(wifi/ble/zigbee/matter/mesh)''');
|
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='product' AND COLUMN_NAME='protocol'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE product ADD COLUMN protocol VARCHAR(32) DEFAULT NULL COMMENT \'联网协议(wifi/ble/zigbee/matter/mesh)\''));
|
||||||
|
PREPARE st FROM @ddl; EXECUTE st; DEALLOCATE PREPARE st;
|
||||||
|
|
||||||
-- 空串会与唯一索引互撞(多个 '' 视为同值),统一清洗为 NULL
|
-- 2.2 空串会与唯一索引互撞(多个空串视为同值),统一清洗为 NULL
|
||||||
UPDATE product SET platform_product_id = NULL WHERE platform_product_id = '';
|
UPDATE product SET platform_product_id = NULL WHERE platform_product_id = '';
|
||||||
|
|
||||||
-- 清洗历史脏数据:model / manufacturer 曾被误写入涂鸦 productId
|
-- 2.3 清洗历史脏数据:model / manufacturer 曾被误写入涂鸦 productId
|
||||||
UPDATE product
|
UPDATE product SET model = NULL, manufacturer = NULL
|
||||||
SET model = NULL, manufacturer = NULL
|
|
||||||
WHERE manufacturer = 'tuya'
|
WHERE manufacturer = 'tuya'
|
||||||
AND model IS NOT NULL
|
AND model IS NOT NULL AND model = platform_product_id;
|
||||||
AND model = platform_product_id;
|
|
||||||
|
|
||||||
-- 守卫:确认无重复后再加唯一约束(有重复时下面 ALTER 会直接报错中止)
|
-- ---------- 3) product_property(物模型属性)----------
|
||||||
-- ---------- 3) product_property(物模型属性) ----------
|
-- 3.1 功能分组:支撑「多开关位整组联动」与详情页分组渲染
|
||||||
-- grp:功能分组(switch / scene / mode / countdown / display ...),
|
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||||
-- 支撑"多开关位一起控制"与详情页分组渲染
|
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='product_property' AND COLUMN_NAME='grp'),
|
||||||
CALL v22_add_col('product_property', 'grp',
|
'SELECT 1',
|
||||||
'VARCHAR(32) DEFAULT NULL COMMENT ''功能分组(switch/scene/mode/countdown/display)''');
|
'ALTER TABLE product_property ADD COLUMN grp VARCHAR(32) DEFAULT NULL COMMENT \'功能分组(switch/scene/mode/countdown/display)\''));
|
||||||
|
PREPARE st FROM @ddl; EXECUTE st; DEALLOCATE PREPARE st;
|
||||||
|
|
||||||
-- is_primary:主开关标记,取代按名称猜测(开关/电源/switch/power)
|
-- 3.2 主开关标记:取代按名称猜测(开关/电源/switch/power)
|
||||||
CALL v22_add_col('product_property', 'is_primary',
|
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||||
'TINYINT NOT NULL DEFAULT 0 COMMENT ''是否主开关(1是,0否)''');
|
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='product_property' AND COLUMN_NAME='is_primary'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE product_property ADD COLUMN is_primary TINYINT NOT NULL DEFAULT 0 COMMENT \'是否主开关(1是,0否)\''));
|
||||||
|
PREPARE st FROM @ddl; EXECUTE st; DEALLOCATE PREPARE st;
|
||||||
|
|
||||||
-- enum_label:枚举中文名 JSON(如 {"warm":"暖风"}),供 APP 与后台显示
|
-- 3.3 枚举中文名 JSON(如 {"warm":"暖风"}),供 APP 与后台显示
|
||||||
CALL v22_add_col('product_property', 'enum_label',
|
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||||
'TEXT DEFAULT NULL COMMENT ''枚举中文名(JSON对象)''');
|
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='product_property' AND COLUMN_NAME='enum_label'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE product_property ADD COLUMN enum_label TEXT DEFAULT NULL COMMENT \'枚举中文名(JSON对象)\''));
|
||||||
|
PREPARE st FROM @ddl; EXECUTE st; DEALLOCATE PREPARE st;
|
||||||
|
|
||||||
|
-- ---------- 4) product_event / product_action ----------
|
||||||
|
-- 4.1 completion_mode 属 action 维度(sync 同步完成 / ack_only 仅受理)
|
||||||
|
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='product_action' AND COLUMN_NAME='completion_mode'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE product_action ADD COLUMN completion_mode VARCHAR(16) DEFAULT \'SYNC\' COMMENT \'完成模式(sync/ack_only)\''));
|
||||||
|
PREPARE st FROM @ddl; EXECUTE st; DEALLOCATE PREPARE st;
|
||||||
|
|
||||||
-- ---------- 4) product_event ----------
|
-- ---------- 5) device ----------
|
||||||
|
-- 5.1 product_id 索引:OTA 按 product_id 物化目标设备,此前为全表扫
|
||||||
|
ALTER TABLE device ADD INDEX idx_product_id (product_id);
|
||||||
|
|
||||||
-- ---------- 5) product_action ----------
|
-- ---------- 6) platform 归一 ----------
|
||||||
-- completion_mode 属 action 维度(sync 同步完成 / ack_only 仅受理)
|
-- 6.1 legacy 别名 -> 规范码
|
||||||
CALL v22_add_col('product_action', 'completion_mode',
|
|
||||||
'VARCHAR(16) DEFAULT ''SYNC'' COMMENT ''完成模式(sync/ack_only)''');
|
|
||||||
|
|
||||||
|
|
||||||
-- ---------- 6) device ----------
|
|
||||||
-- product_id 索引:OTA 按 product_id 物化目标设备,此前为全表扫
|
|
||||||
CALL v22_add_idx('device', 'idx_product_id', 'product_id');
|
|
||||||
|
|
||||||
-- platform 归一:legacy 别名 -> 规范码
|
|
||||||
UPDATE device SET platform = 'xiaomi' WHERE platform = 'miot';
|
UPDATE device SET platform = 'xiaomi' WHERE platform = 'miot';
|
||||||
|
|
||||||
-- platform 空值兜底为 local(无平台信息的中控屏本地设备)
|
-- 6.2 platform 空值兜底为 local(无平台信息的中控屏本地设备)
|
||||||
UPDATE device SET platform = 'local' WHERE platform IS NULL OR platform = '';
|
UPDATE device SET platform = 'local' WHERE platform IS NULL OR platform = '';
|
||||||
|
|
||||||
-- 被误标为 local、但持有云平台产品ID 的设备 -> 归回涂鸦
|
-- 6.3 被误标为 local、但持有云平台产品ID 的设备 -> 归回涂鸦
|
||||||
UPDATE device SET platform = 'tuya'
|
UPDATE device SET platform = 'tuya'
|
||||||
WHERE platform = 'local'
|
WHERE platform = 'local'
|
||||||
AND platform_product_id IS NOT NULL
|
AND platform_product_id IS NOT NULL AND platform_product_id <> '';
|
||||||
AND platform_product_id <> '';
|
|
||||||
|
|
||||||
-- ---------- 9) 唯一索引(统一放在软删重复行清理之后执行,避免创建失败)----------
|
-- ---------- 7) 唯一索引前的软删重复行清理 ----------
|
||||||
CALL v22_add_uniq('product', 'uk_platform_product', 'platform, platform_product_id, deleted');
|
-- 唯一索引 (product_id, code, deleted) 对「同 code 多条软删行」同样冲突
|
||||||
CALL v22_add_uniq('product_property', 'uk_product_code', 'product_id, code, deleted');
|
--(deleted 值相同=1)。物模型是声明式数据,多余软删行无价值,物理清理:
|
||||||
CALL v22_add_uniq('product_event', 'uk_product_code', 'product_id, code, deleted');
|
DELETE p FROM product_property p JOIN product_property k
|
||||||
CALL v22_add_uniq('product_action', 'uk_product_code', 'product_id, code, deleted');
|
ON k.product_id = p.product_id AND k.code = p.code AND k.id < p.id WHERE p.deleted = 1;
|
||||||
|
DELETE e FROM product_event e JOIN product_event k
|
||||||
-- ---------- 7) 清理辅助过程 ----------
|
ON k.product_id = e.product_id AND k.code = e.code AND k.id < e.id WHERE e.deleted = 1;
|
||||||
DROP PROCEDURE IF EXISTS v22_add_col$$
|
DELETE a FROM product_action a JOIN product_action k
|
||||||
DROP PROCEDURE IF EXISTS v22_add_idx$$
|
ON k.product_id = a.product_id AND k.code = a.code AND k.id < a.id WHERE a.deleted = 1;
|
||||||
DROP PROCEDURE IF EXISTS v22_add_uniq$$
|
DELETE p FROM product p JOIN product k
|
||||||
-- ---------- 8) 唯一索引前的软删重复行清理 ----------
|
ON k.platform = p.platform AND k.platform_product_id = p.platform_product_id
|
||||||
-- 唯一索引 (product_id, code, deleted) 对"同 code 多条软删行"同样会冲突
|
AND k.id < p.id WHERE p.deleted = 1 AND p.platform_product_id IS NOT NULL;
|
||||||
-- (deleted 值相同=1)。物模型是声明式数据,多余软删行无价值,此处物理清理:
|
|
||||||
-- 每个 (product_id, code) 只保留 id 最小的一条,其余物理删除。
|
|
||||||
DELETE p FROM product_property p
|
|
||||||
JOIN product_property k
|
|
||||||
ON k.product_id = p.product_id
|
|
||||||
AND k.code = p.code
|
|
||||||
AND k.id < p.id
|
|
||||||
WHERE p.deleted = 1;
|
|
||||||
|
|
||||||
DELETE e FROM product_event e
|
|
||||||
JOIN product_event k
|
|
||||||
ON k.product_id = e.product_id
|
|
||||||
AND k.code = e.code
|
|
||||||
AND k.id < e.id
|
|
||||||
WHERE e.deleted = 1;
|
|
||||||
|
|
||||||
DELETE a FROM product_action a
|
|
||||||
JOIN product_action k
|
|
||||||
ON k.product_id = a.product_id
|
|
||||||
AND k.code = a.code
|
|
||||||
AND k.id < a.id
|
|
||||||
WHERE a.deleted = 1;
|
|
||||||
|
|
||||||
DELETE p FROM product p
|
|
||||||
JOIN product k
|
|
||||||
ON k.platform = p.platform
|
|
||||||
AND k.platform_product_id = p.platform_product_id
|
|
||||||
AND k.id < p.id
|
|
||||||
WHERE p.deleted = 1
|
|
||||||
AND p.platform_product_id IS NOT NULL;
|
|
||||||
|
|
||||||
|
-- ---------- 8) 唯一索引(统一放在软删重复行清理之后,避免创建失败)----------
|
||||||
|
ALTER TABLE product ADD UNIQUE INDEX uk_platform_product (platform, platform_product_id, deleted);
|
||||||
|
ALTER TABLE product_property ADD UNIQUE INDEX uk_product_code (product_id, code, deleted);
|
||||||
|
ALTER TABLE product_event ADD UNIQUE INDEX uk_product_code (product_id, code, deleted);
|
||||||
|
ALTER TABLE product_action ADD UNIQUE INDEX uk_product_code (product_id, code, deleted);
|
||||||
|
|||||||
@ -116,7 +116,8 @@ if not "!CHK!"=="0" (
|
|||||||
goto :eof
|
goto :eof
|
||||||
)
|
)
|
||||||
echo 应用 %1 ...
|
echo 应用 %1 ...
|
||||||
type "%MIG_DIR%\%1" | %PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform"
|
REM --force: 迁移脚本中"已存在"的 DDL(重复列/索引)属正常情况,不应中断整体部署
|
||||||
|
type "%MIG_DIR%\%1" | %PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql --force -uroot -p'IoT.p2026.Xk9Qm' iot_platform"
|
||||||
if !ERRORLEVEL! NEQ 0 ( echo [错误] 迁移 %1 执行失败。 & pause & exit /b 1 )
|
if !ERRORLEVEL! NEQ 0 ( echo [错误] 迁移 %1 执行失败。 & pause & exit /b 1 )
|
||||||
%PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform -e \"INSERT IGNORE INTO db_migrations_applied(name) VALUES('%1')\"" 2>nul
|
%PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform -e \"INSERT IGNORE INTO db_migrations_applied(name) VALUES('%1')\"" 2>nul
|
||||||
goto :eof
|
goto :eof
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user