Compare commits
No commits in common. "b2d758be333e5df8a654e9e04473196d6ba0a4ef" and "297206368eea385ee41fe3933d31e10bb5b6a7e8" have entirely different histories.
b2d758be33
...
297206368e
@ -1,21 +0,0 @@
|
||||
[Unit]
|
||||
Description=IoT Alarm Service (9084)
|
||||
After=docker.service network.target
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=tonzou
|
||||
WorkingDirectory=/opt/iotsaas/backend/jars
|
||||
# 日志根目录:logback-spring.xml 中 ${LOG_DIR:-logs/app} 读取该环境变量
|
||||
Environment="LOG_DIR=/opt/iotsaas/logs/app"
|
||||
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/iotsaas/backend/jars/iot-alarm-1.0.0.jar
|
||||
SuccessExitStatus=143
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
# 控制台输出交由 systemd journal(自带轮转与体积上限),不再写无轮转的 *.out
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -1,21 +0,0 @@
|
||||
[Unit]
|
||||
Description=IoT Data Collection Service (9082)
|
||||
After=docker.service network.target
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=tonzou
|
||||
WorkingDirectory=/opt/iotsaas/backend/jars
|
||||
# 日志根目录:logback-spring.xml 中 ${LOG_DIR:-logs/app} 读取该环境变量
|
||||
Environment="LOG_DIR=/opt/iotsaas/logs/app"
|
||||
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/iotsaas/backend/jars/iot-data-1.0.0.jar
|
||||
SuccessExitStatus=143
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
# 控制台输出交由 systemd journal(自带轮转与体积上限),不再写无轮转的 *.out
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -1,23 +0,0 @@
|
||||
[Unit]
|
||||
Description=IoT Device Service (9081)
|
||||
After=docker.service network.target
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=tonzou
|
||||
WorkingDirectory=/opt/iotsaas/backend/jars
|
||||
# 日志根目录:logback-spring.xml 中 ${LOG_DIR:-logs/app} 读取该环境变量,
|
||||
# 滚动日志落到 /opt/iotsaas/logs/app/iot-device.log(按日+50MB 切分,保留 7 天)
|
||||
Environment="LOG_DIR=/opt/iotsaas/logs/app"
|
||||
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/iotsaas/backend/jars/iot-device-1.0.0-exec.jar
|
||||
SuccessExitStatus=143
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
# 控制台输出交由 systemd journal(自带轮转与体积上限),
|
||||
# 不再写 append:*.out 这类无轮转文件(曾膨胀到 613MB)
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -1,21 +0,0 @@
|
||||
[Unit]
|
||||
Description=IoT Rule Engine Service (9083)
|
||||
After=docker.service network.target
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=tonzou
|
||||
WorkingDirectory=/opt/iotsaas/backend/jars
|
||||
# 日志根目录:logback-spring.xml 中 ${LOG_DIR:-logs/app} 读取该环境变量
|
||||
Environment="LOG_DIR=/opt/iotsaas/logs/app"
|
||||
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/iotsaas/backend/jars/iot-rule-1.0.0.jar
|
||||
SuccessExitStatus=143
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
# 控制台输出交由 systemd journal(自带轮转与体积上限),不再写无轮转的 *.out
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -1,8 +0,0 @@
|
||||
[Journal]
|
||||
# iotsaas 日志治理:限制 journal 体积,避免 /var/log/journal 无限增长
|
||||
# (Ubuntu 默认 SystemMaxUse = 磁盘的 10%,本机 1.8T 盘即 180G,过大)
|
||||
Storage=persistent
|
||||
SystemMaxUse=1G
|
||||
SystemMaxFileSize=50M
|
||||
SystemMaxFileSec=1day
|
||||
MaxRetentionSec=7day
|
||||
@ -95,10 +95,7 @@ mybatis-plus:
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
# 显式指定日志配置:涂鸦 open-mq-sdk jar 内自带 logback.xml 会被抢占,
|
||||
# 强制使用统一配置(文件本体在 iot-common,构建时复制到本模块),含滚动切分与保留策略
|
||||
config: classpath:logback-spring.xml
|
||||
level:
|
||||
root: INFO
|
||||
com.iot: INFO
|
||||
com.iot.alarm: INFO
|
||||
com.iot: DEBUG
|
||||
com.iot.alarm: DEBUG
|
||||
|
||||
@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
iotsaas 统一日志配置(唯一真相源,iot-device / iot-data / iot-rule / iot-alarm 四个服务共用)。
|
||||
|
||||
存放位置:iot-common/src/main/resources/logback-spring.xml
|
||||
生效方式:各服务 application.yml 中显式声明 logging.config: classpath:logback-spring.xml
|
||||
(构建时由父 pom 的 maven-resources-plugin 复制到各服务 BOOT-INF/classes/)
|
||||
|
||||
为什么必须显式指定:
|
||||
涂鸦 open-mq-sdk jar 内自带 logback.xml,会被 logback 标准查找逻辑命中并抢占日志配置
|
||||
(root=DEBUG、日志写到 ./logs 相对目录、只按小时切分),曾导致 /opt/iotsaas/logs/iot-device.out
|
||||
暴涨到 613MB。本配置通过 logging.config 显式指定,优先级最高,确保生效。
|
||||
|
||||
轮转策略:按日切分 + 单文件 50MB 上限,保留 7 天,主日志总量上限 2GB,错误日志 512MB。
|
||||
日志目录:默认相对工作目录 logs/app(本地开发),生产由 systemd 注入环境变量
|
||||
LOG_DIR=/opt/iotsaas/logs/app。
|
||||
-->
|
||||
<configuration>
|
||||
<!-- 服务名:取自各服务 application.yml 的 spring.application.name -->
|
||||
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="iot-app"/>
|
||||
<!-- 日志根目录 -->
|
||||
<property name="LOG_DIR" value="${LOG_DIR:-logs/app}"/>
|
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
|
||||
<property name="LOG_FILE" value="${LOG_DIR}/${APP_NAME}.log"/>
|
||||
<property name="LOG_FILE_PATTERN" value="${LOG_DIR}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log.gz"/>
|
||||
<property name="ERR_FILE" value="${LOG_DIR}/${APP_NAME}-error.log"/>
|
||||
<property name="ERR_FILE_PATTERN" value="${LOG_DIR}/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log.gz"/>
|
||||
|
||||
<!-- 控制台:交给 systemd journal(journalctl -u iot-device -f 可查) -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 主日志文件:按日 + 50MB 双触发切分,保留 7 天,总量上限 2GB -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_FILE}</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE_PATTERN}</fileNamePattern>
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
<maxHistory>7</maxHistory>
|
||||
<totalSizeCap>2GB</totalSizeCap>
|
||||
<cleanHistoryOnStart>true</cleanHistoryOnStart>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 错误日志单独一份,便于快速定位故障;保留 30 天 -->
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${ERR_FILE}</file>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${ERR_FILE_PATTERN}</fileNamePattern>
|
||||
<maxFileSize>20MB</maxFileSize>
|
||||
<maxHistory>30</maxHistory>
|
||||
<totalSizeCap>512MB</totalSizeCap>
|
||||
<cleanHistoryOnStart>true</cleanHistoryOnStart>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 第三方 SDK 降噪:涂鸦 / MQTT / 消息中间件只保留 WARN 以上 -->
|
||||
<logger name="com.tuya" level="WARN"/>
|
||||
<logger name="openmq" level="WARN"/>
|
||||
<logger name="org.eclipse.paho" level="WARN"/>
|
||||
<logger name="org.springframework.amqp" level="WARN"/>
|
||||
<logger name="com.aliyun" level="WARN"/>
|
||||
<!-- 业务包:INFO(MyBatis mapper SQL 日志随此级别收敛,避免刷屏) -->
|
||||
<logger name="com.iot" level="INFO"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@ -84,10 +84,7 @@ mybatis-plus:
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
# 显式指定日志配置:涂鸦 open-mq-sdk jar 内自带 logback.xml 会被抢占,
|
||||
# 强制使用统一配置(文件本体在 iot-common,构建时复制到本模块),含滚动切分与保留策略
|
||||
config: classpath:logback-spring.xml
|
||||
level:
|
||||
root: INFO
|
||||
com.iot: INFO
|
||||
com.iot.data: INFO
|
||||
com.iot: DEBUG
|
||||
com.iot.data: DEBUG
|
||||
|
||||
@ -3,10 +3,8 @@ package com.iot.device.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.iot.device.entity.ProductAction;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface ProductActionMapper extends BaseMapper<ProductAction> {
|
||||
|
||||
/**
|
||||
|
||||
@ -3,10 +3,8 @@ package com.iot.device.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.iot.device.entity.ProductEvent;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface ProductEventMapper extends BaseMapper<ProductEvent> {
|
||||
|
||||
/**
|
||||
|
||||
@ -111,14 +111,14 @@ xxl:
|
||||
logging:
|
||||
# 显式指定日志配置:涂鸦 open-mq-sdk jar 内自带 logback.xml 会被标准 logback
|
||||
# 自动初始化命中并抢占日志输出(控制台无日志、日志写入 ./logs/ 相对目录),
|
||||
# 这里强制使用本应用的 logback-spring.xml 覆盖之(文件本体在 iot-common,构建时复制到本模块)
|
||||
# 这里强制使用本应用的 logback-spring.xml 覆盖之
|
||||
config: classpath:logback-spring.xml
|
||||
level:
|
||||
root: INFO
|
||||
# 生产环境保持 INFO:MyBatis mapper 的 SQL 日志在 DEBUG 级别,
|
||||
# 打开后每条 SQL 打印两次,单日可产生上万条日志并撑大日志文件
|
||||
com.iot: INFO
|
||||
com.iot.device: INFO
|
||||
com.iot: DEBUG
|
||||
com.iot.device: DEBUG
|
||||
pattern:
|
||||
console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
|
||||
|
||||
# Swagger配置
|
||||
knife4j:
|
||||
|
||||
15
backend/iot-device/src/main/resources/logback-spring.xml
Normal file
15
backend/iot-device/src/main/resources/logback-spring.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
恢复 Spring Boot 标准日志配置。
|
||||
背景:涂鸦 open-mq-sdk jar 内自带 logback.xml,被 logback 标准查找逻辑命中后
|
||||
会抢占日志输出(控制台无日志、日志被写到 ./logs/ 相对目录)。
|
||||
Spring Boot 的查找顺序为 logback-test.xml → logback-spring.xml → logback.xml,
|
||||
本文件(logback-spring.xml)优先级高于 jar 内的 logback.xml,从而覆盖之。
|
||||
-->
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@ -97,11 +97,8 @@ mybatis-plus:
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
# 显式指定日志配置:涂鸦 open-mq-sdk jar 内自带 logback.xml 会被抢占,
|
||||
# 强制使用统一配置(文件本体在 iot-common,构建时复制到本模块),含滚动切分与保留策略
|
||||
config: classpath:logback-spring.xml
|
||||
level:
|
||||
root: INFO
|
||||
com.iot: INFO
|
||||
com.iot.rule: INFO
|
||||
com.iot: DEBUG
|
||||
com.iot.rule: DEBUG
|
||||
org.drools: INFO
|
||||
|
||||
@ -199,37 +199,6 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<!--
|
||||
统一日志配置:把 iot-common 的 logback-spring.xml 复制到各服务的 BOOT-INF/classes。
|
||||
目的:让 4 个服务都带上同一份日志配置,从而稳定覆盖涂鸦 open-mq-sdk jar 内自带的
|
||||
logback.xml(该配置 root=DEBUG 且只写 ./logs 相对目录,曾导致日志文件无限膨胀)。
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-logback-config</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
|
||||
<overwrite>true</overwrite>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.basedir}/../iot-common/src/main/resources</directory>
|
||||
<includes>
|
||||
<include>logback-spring.xml</include>
|
||||
</includes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@ -1,25 +1,73 @@
|
||||
-- =====================================================================
|
||||
-- V22 · 产品 / 物模型 / 设备 三层架构重构(地基)
|
||||
-- ---------------------------------------------------------------------
|
||||
-- 执行特性(重要,实测结论):
|
||||
-- 本脚本【不使用存储过程、不使用 DELIMITER】,全部是普通语句序列。
|
||||
-- 原因:MySQL 客户端在 batch 模式(管道 / 文件重定向 / source)下
|
||||
-- 会把 DELIMITER 当作普通 SQL 发给服务器,导致含存储过程的脚本
|
||||
-- 100% 执行失败(已在生产库克隆上实测复现)。生产库现存存储过程数为 0,
|
||||
-- 即历史 V19/V21 等含 DELIMITER 的脚本其存储过程部分从未生效。
|
||||
-- 幂等性改由 PREPARE + information_schema 判定实现,语义等价。
|
||||
-- 库名一律用 DATABASE() 判定,测试库克隆无需改写脚本。
|
||||
-- ---------------------------------------------------------------------
|
||||
-- 本次动作(仅结构与数据清洗;业务语义回填由后端 SpecSemanticResolver
|
||||
-- 单一权威实现,保证规则只有一份):
|
||||
-- 背景(重构前的实测断点):
|
||||
-- 1) platform 取值域四套并存:product(tuya/local/matter) /
|
||||
-- device(tuya/miot/dueros/local/matter) / DevicePlatform 枚举 /
|
||||
-- platform_config(tuya/aliyun/xfyun),miot、dueros 设备无处归类;
|
||||
-- 2) 物模型两套并存:product_property(唯一被消费)与 device.schema_json
|
||||
-- (中控屏上报后只写不读,属死数据);
|
||||
-- 3) product_property 无 (product_id, code) 唯一约束,saveSpec 全量替换
|
||||
-- / saveProperty 增量 / reconcile-spec 物理删三条写路径语义不一;
|
||||
-- 4) product.model / manufacturer 被涂鸦 productId 污染
|
||||
-- (resolveProductId 调用方参数错位),product_key 为随机 UUID 不可读;
|
||||
-- 5) device.product_id 无索引,而 OTA 按 product_id 物化目标设备 -> 全表扫;
|
||||
-- 6) V19 已建 product_property.semantic/channel/completionMode 三列,
|
||||
-- 但 Entity / DTO 全无对应字段 -> 语义字段整链断裂。
|
||||
--
|
||||
-- 本次动作(仅结构与数据清洗,不做业务语义回填;语义回填由后端
|
||||
-- SpecSemanticResolver 单一权威实现,保证规则只有一份):
|
||||
-- A. 新建 platform_type 平台字典表(后台可维护,取代硬编码枚举)
|
||||
-- B. product:加 protocol 列;platform_product_id 空串清洗为 NULL;
|
||||
-- 清洗 model/manufacturer 脏数据;加平台唯一约束
|
||||
-- C. product_property:加 grp / is_primary / enum_label;加功能点唯一约束
|
||||
-- D. product_event / product_action:加功能点唯一约束;action 加 completion_mode
|
||||
-- B. product:加 protocol 列;空串 platform_product_id -> NULL;
|
||||
-- 清洗 model/manufacturer 脏数据;加 (platform, platform_product_id) 唯一约束
|
||||
-- C. product_property:加 grp / is_primary / enum_label;
|
||||
-- 加 (product_id, code) 唯一约束
|
||||
-- D. product_event / product_action:加 (product_id, code) 唯一约束;
|
||||
-- product_action 加 completion_mode
|
||||
-- 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) 平台字典表 ----------
|
||||
CREATE TABLE IF NOT EXISTS platform_type (
|
||||
id BIGINT NOT NULL COMMENT '主键',
|
||||
@ -52,86 +100,108 @@ VALUES
|
||||
(2260000000000000007, 'alexa', 'Amazon Alexa', 'alexa', 'wifi', 0, 70, '待接入', NOW(), NOW(), 0),
|
||||
(2260000000000000008, 'google_home', 'Google Home', 'google_home', 'wifi', 0, 80, '待接入', NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name), legacy_codes = VALUES(legacy_codes), protocol = VALUES(protocol),
|
||||
sort = VALUES(sort), remark = VALUES(remark), update_time = NOW();
|
||||
name = VALUES(name),
|
||||
legacy_codes = VALUES(legacy_codes),
|
||||
protocol = VALUES(protocol),
|
||||
sort = VALUES(sort),
|
||||
remark = VALUES(remark),
|
||||
update_time = NOW();
|
||||
|
||||
-- ---------- 2) product ----------
|
||||
-- 2.1 协议列(与 platform 正交):wifi / ble / zigbee / matter / mesh
|
||||
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||
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;
|
||||
-- 协议维度(与 platform 正交):wifi / ble / zigbee / matter / mesh
|
||||
CALL v22_add_col('product', 'protocol',
|
||||
'VARCHAR(32) DEFAULT NULL COMMENT ''联网协议(wifi/ble/zigbee/matter/mesh)''');
|
||||
|
||||
-- 2.2 空串会与唯一索引互撞(多个空串视为同值),统一清洗为 NULL
|
||||
-- 空串会与唯一索引互撞(多个 '' 视为同值),统一清洗为 NULL
|
||||
UPDATE product SET platform_product_id = NULL WHERE platform_product_id = '';
|
||||
|
||||
-- 2.3 清洗历史脏数据:model / manufacturer 曾被误写入涂鸦 productId
|
||||
UPDATE product SET model = NULL, manufacturer = NULL
|
||||
-- 清洗历史脏数据:model / manufacturer 曾被误写入涂鸦 productId
|
||||
UPDATE product
|
||||
SET model = NULL, manufacturer = NULL
|
||||
WHERE manufacturer = 'tuya'
|
||||
AND model IS NOT NULL AND model = platform_product_id;
|
||||
AND model IS NOT NULL
|
||||
AND model = platform_product_id;
|
||||
|
||||
-- ---------- 3) product_property(物模型属性)----------
|
||||
-- 3.1 功能分组:支撑「多开关位整组联动」与详情页分组渲染
|
||||
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='product_property' AND COLUMN_NAME='grp'),
|
||||
'SELECT 1',
|
||||
'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;
|
||||
-- 守卫:确认无重复后再加唯一约束(有重复时下面 ALTER 会直接报错中止)
|
||||
-- ---------- 3) product_property(物模型属性) ----------
|
||||
-- grp:功能分组(switch / scene / mode / countdown / display ...),
|
||||
-- 支撑"多开关位一起控制"与详情页分组渲染
|
||||
CALL v22_add_col('product_property', 'grp',
|
||||
'VARCHAR(32) DEFAULT NULL COMMENT ''功能分组(switch/scene/mode/countdown/display)''');
|
||||
|
||||
-- 3.2 主开关标记:取代按名称猜测(开关/电源/switch/power)
|
||||
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||
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;
|
||||
-- is_primary:主开关标记,取代按名称猜测(开关/电源/switch/power)
|
||||
CALL v22_add_col('product_property', 'is_primary',
|
||||
'TINYINT NOT NULL DEFAULT 0 COMMENT ''是否主开关(1是,0否)''');
|
||||
|
||||
-- 3.3 枚举中文名 JSON(如 {"warm":"暖风"}),供 APP 与后台显示
|
||||
SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.COLUMNS
|
||||
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;
|
||||
-- enum_label:枚举中文名 JSON(如 {"warm":"暖风"}),供 APP 与后台显示
|
||||
CALL v22_add_col('product_property', 'enum_label',
|
||||
'TEXT DEFAULT NULL COMMENT ''枚举中文名(JSON对象)''');
|
||||
|
||||
-- ---------- 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;
|
||||
|
||||
-- ---------- 5) device ----------
|
||||
-- 5.1 product_id 索引:OTA 按 product_id 物化目标设备,此前为全表扫
|
||||
ALTER TABLE device ADD INDEX idx_product_id (product_id);
|
||||
-- ---------- 4) product_event ----------
|
||||
|
||||
-- ---------- 6) platform 归一 ----------
|
||||
-- 6.1 legacy 别名 -> 规范码
|
||||
-- ---------- 5) product_action ----------
|
||||
-- completion_mode 属 action 维度(sync 同步完成 / ack_only 仅受理)
|
||||
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';
|
||||
|
||||
-- 6.2 platform 空值兜底为 local(无平台信息的中控屏本地设备)
|
||||
-- platform 空值兜底为 local(无平台信息的中控屏本地设备)
|
||||
UPDATE device SET platform = 'local' WHERE platform IS NULL OR platform = '';
|
||||
|
||||
-- 6.3 被误标为 local、但持有云平台产品ID 的设备 -> 归回涂鸦
|
||||
-- 被误标为 local、但持有云平台产品ID 的设备 -> 归回涂鸦
|
||||
UPDATE device SET platform = 'tuya'
|
||||
WHERE platform = 'local'
|
||||
AND platform_product_id IS NOT NULL AND platform_product_id <> '';
|
||||
AND platform_product_id IS NOT NULL
|
||||
AND platform_product_id <> '';
|
||||
|
||||
-- ---------- 7) 唯一索引前的软删重复行清理 ----------
|
||||
-- 唯一索引 (product_id, code, deleted) 对「同 code 多条软删行」同样冲突
|
||||
--(deleted 值相同=1)。物模型是声明式数据,多余软删行无价值,物理清理:
|
||||
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;
|
||||
-- ---------- 9) 唯一索引(统一放在软删重复行清理之后执行,避免创建失败)----------
|
||||
CALL v22_add_uniq('product', 'uk_platform_product', 'platform, platform_product_id, deleted');
|
||||
CALL v22_add_uniq('product_property', 'uk_product_code', 'product_id, code, deleted');
|
||||
CALL v22_add_uniq('product_event', 'uk_product_code', 'product_id, code, deleted');
|
||||
CALL v22_add_uniq('product_action', 'uk_product_code', 'product_id, code, deleted');
|
||||
|
||||
-- ---------- 7) 清理辅助过程 ----------
|
||||
DROP PROCEDURE IF EXISTS v22_add_col$$
|
||||
DROP PROCEDURE IF EXISTS v22_add_idx$$
|
||||
DROP PROCEDURE IF EXISTS v22_add_uniq$$
|
||||
-- ---------- 8) 唯一索引前的软删重复行清理 ----------
|
||||
-- 唯一索引 (product_id, code, deleted) 对"同 code 多条软删行"同样会冲突
|
||||
-- (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);
|
||||
|
||||
@ -25,7 +25,7 @@ set JAR_ALARM=iot-alarm-1.0.0.jar
|
||||
:: ============ 1) 后端构建(含共享模块 iot-common) ============
|
||||
cd /d %BACKEND_DIR%
|
||||
echo ============================================
|
||||
echo [1/8] Maven 重新构建 4 个服务(含共享模块 iot-common)
|
||||
echo [1/7] Maven 重新构建 4 个服务(含共享模块 iot-common)
|
||||
echo ============================================
|
||||
call mvn clean package -pl iot-device,iot-data,iot-rule,iot-alarm -am -DskipTests
|
||||
if %ERRORLEVEL% NEQ 0 ( echo [错误] 后端构建失败,已中止。 & pause & exit /b 1 )
|
||||
@ -33,7 +33,7 @@ echo [OK] 后端构建成功。
|
||||
|
||||
:: ============ 2) 上传 4 个 jar ============
|
||||
echo ============================================
|
||||
echo [2/8] 上传 4 个 jar 到服务器 %SRV_JARS%
|
||||
echo [2/7] 上传 4 个 jar 到服务器 %SRV_JARS%
|
||||
echo ============================================
|
||||
tar cf - -C %BACKEND_DIR%\iot-device\target %JAR_DEV% -C %BACKEND_DIR%\iot-data\target %JAR_DATA% -C %BACKEND_DIR%\iot-rule\target %JAR_RULE% -C %BACKEND_DIR%\iot-alarm\target %JAR_ALARM% | %PLINK% %PLINK_OPTS% tar xf - -C %SRV_JARS%
|
||||
if %ERRORLEVEL% NEQ 0 ( echo [错误] jar 上传失败。 & pause & exit /b 1 )
|
||||
@ -41,7 +41,7 @@ echo [OK] jar 上传完成。
|
||||
|
||||
:: ============ 2.5) 数据库迁移(Flyway 在本环境未启用,按 DB 内 tracking 表应用未执行的 V 迁移) ============
|
||||
echo ============================================
|
||||
echo [3/8] 应用未执行的 SQL 迁移(%BACKEND_DIR%\sql\V*.sql,按 db_migrations_applied 去重)
|
||||
echo [3/7] 应用未执行的 SQL 迁移(%BACKEND_DIR%\sql\V*.sql,按 db_migrations_applied 去重)
|
||||
echo ============================================
|
||||
set MIG_DIR=%BACKEND_DIR%\sql
|
||||
set CHK_FILE=%TEMP%\_mig_chk.txt
|
||||
@ -58,23 +58,9 @@ REM 应用未记录的 V 迁移(新增迁移文件会自动在此补跑,无
|
||||
for /f "delims=" %%F in ('dir /b /on "%MIG_DIR%\V*.sql"') do call :mig_apply "%%F"
|
||||
echo [OK] 迁移步骤完成。
|
||||
|
||||
::: ============ 3.5) 日志治理:同步 systemd 单元 + journald 限额,清理无轮转的历史 .out ============
|
||||
echo ============================================
|
||||
echo [4/8] 同步 systemd 单元与 journald 限额(日志轮转治理),并清理历史无轮转 .out/.err
|
||||
echo ============================================
|
||||
%PLINK% %PLINK_OPTS% "rm -rf /tmp/iotsaas-units && mkdir -p /tmp/iotsaas-units"
|
||||
tar cf - -C %BACKEND_DIR%\deploy\systemd . | %PLINK% %PLINK_OPTS% tar xf - -C /tmp/iotsaas-units
|
||||
if %ERRORLEVEL% NEQ 0 ( echo [错误] systemd 单元上传失败。 & pause & exit /b 1 )
|
||||
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' cp /tmp/iotsaas-units/iot-device.service /tmp/iotsaas-units/iot-data.service /tmp/iotsaas-units/iot-rule.service /tmp/iotsaas-units/iot-alarm.service /etc/systemd/system/ && echo 'Tonzou@888' | sudo -S -p '' mkdir -p /etc/systemd/journald.conf.d && echo 'Tonzou@888' | sudo -S -p '' cp /tmp/iotsaas-units/journald-iotsaas.conf /etc/systemd/journald.conf.d/iotsaas.conf"
|
||||
if %ERRORLEVEL% NEQ 0 ( echo [错误] systemd 单元安装失败。 & pause & exit /b 1 )
|
||||
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' systemctl daemon-reload && echo 'Tonzou@888' | sudo -S -p '' systemctl restart systemd-journald"
|
||||
REM 清理历史无轮转的 .out/.err(内容已由 journal 与滚动日志文件接管),并准备滚动日志目录
|
||||
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' rm -f /opt/iotsaas/logs/*.out /opt/iotsaas/logs/*.err && echo 'Tonzou@888' | sudo -S -p '' rm -rf /opt/iotsaas/backend/jars/logs && echo 'Tonzou@888' | sudo -S -p '' mkdir -p /opt/iotsaas/logs/app && echo 'Tonzou@888' | sudo -S -p '' chown -R tonzou:tonzou /opt/iotsaas/logs"
|
||||
echo [OK] systemd 单元与 journald 限额已同步,历史 .out 已清理。
|
||||
|
||||
:: ============ 3) 重启 4 个后端服务 ============
|
||||
echo ============================================
|
||||
echo [5/8] 重启 4 个后端服务(systemd)
|
||||
echo [4/7] 重启 4 个后端服务(systemd)
|
||||
echo ============================================
|
||||
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' systemctl restart iot-device iot-data iot-rule iot-alarm" 2>nul
|
||||
if %ERRORLEVEL% NEQ 0 ( echo [错误] 服务重启失败。 & pause & exit /b 1 )
|
||||
@ -83,7 +69,7 @@ echo [OK] 重启命令已下发。
|
||||
:: ============ 4) 构建前端后台(admin) ============
|
||||
cd /d %ADMIN_DIR%
|
||||
echo ============================================
|
||||
echo [6/8] 构建前端后台(admin)— 跳过 vue-tsc 类型检查,直接 vite build
|
||||
echo [5/7] 构建前端后台(admin)— 跳过 vue-tsc 类型检查,直接 vite build
|
||||
echo ============================================
|
||||
where node >nul 2>nul || ( echo [错误] 未找到 node,请将其加入 PATH。 & pause & exit /b 1 )
|
||||
call npx vite build
|
||||
@ -92,7 +78,7 @@ echo [OK] 前端构建成功(dist 已生成)。
|
||||
|
||||
:: ============ 5) 上传前端 dist(先清空旧文件再解包,避免残留旧 hash 资源) ============
|
||||
echo ============================================
|
||||
echo [7/8] 上传前端 dist 到服务器 %SRV_DIST%
|
||||
echo [6/7] 上传前端 dist 到服务器 %SRV_DIST%
|
||||
echo ============================================
|
||||
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' rm -rf %SRV_DIST% && mkdir -p %SRV_DIST%"
|
||||
if %ERRORLEVEL% NEQ 0 ( echo [错误] 清空远端 dist 失败。 & pause & exit /b 1 )
|
||||
@ -102,7 +88,7 @@ echo [OK] dist 上传完成。
|
||||
|
||||
:: ============ 6) 确认状态 ============
|
||||
echo ============================================
|
||||
echo [8/8] 等待启动并确认服务状态
|
||||
echo [7/7] 等待启动并确认服务状态
|
||||
echo ============================================
|
||||
timeout /t 20 /nobreak >nul
|
||||
%PLINK% %PLINK_OPTS% "echo 'Tonzou@888' | sudo -S -p '' systemctl is-active iot-device iot-data iot-rule iot-alarm" 2>nul
|
||||
@ -130,8 +116,7 @@ if not "!CHK!"=="0" (
|
||||
goto :eof
|
||||
)
|
||||
echo 应用 %1 ...
|
||||
REM --force: 迁移脚本中"已存在"的 DDL(重复列/索引)属正常情况,不应中断整体部署
|
||||
type "%MIG_DIR%\%1" | %PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql --force -uroot -p'IoT.p2026.Xk9Qm' iot_platform"
|
||||
type "%MIG_DIR%\%1" | %PLINK% %PLINK_OPTS% "docker exec -i iot-mysql mysql -uroot -p'IoT.p2026.Xk9Qm' iot_platform"
|
||||
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
|
||||
goto :eof
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user