Просмотр исходного кода

Merge branch 'mqy20260511' of http://114.215.146.132:3000/Mqy/Wisdom-Data into mqy20260511

mqy20260511
lenovo 2 недель назад
Родитель
Сommit
6ec2442651

+ 24
- 0
.env Просмотреть файл

1
+# IoT 平台环境变量配置
2
+# 启动前自动加载:./start.sh start
3
+# 或手动加载:source .env
4
+
5
+# MySQL 数据库(主从共用)
6
+MYSQL_USERNAME=root
7
+MYSQL_PASSWORD="Zhu059300()__"
8
+
9
+# Redis
10
+REDIS_PASSWORD=
11
+
12
+# MQTT Broker
13
+MQTT_USERNAME=NjniyrEO
14
+MQTT_PASSWORD=2b577892f4824d466dbc323a1ee4dfe1902c55bb
15
+
16
+# TDengine 时序数据库
17
+TDENGINE_USERNAME=root
18
+TDENGINE_PASSWORD=taosdata
19
+
20
+# Druid 监控(生产环境建议设为 false 关闭)
21
+DRUID_STAT_ENABLED=true
22
+DRUID_USERNAME=ruoyi
23
+DRUID_PASSWORD=123456
24
+REDIS_HOST=172.21.185.173

+ 2
- 1
.gitignore Просмотреть файл

50
 .claude/
50
 .claude/
51
 
51
 
52
 # Environment variables
52
 # Environment variables
53
-.env
53
+# .env is now tracked in git per user request
54
+# .env
54
 !.env.example
55
 !.env.example

+ 4
- 3
CLAUDE.md Просмотреть файл

9
 - **Backend**: Spring Boot 2.5.15, Java 8, Spring Security 5.7.12, JWT authentication
9
 - **Backend**: Spring Boot 2.5.15, Java 8, Spring Security 5.7.12, JWT authentication
10
 - **Frontend**: Vue 2 + Element UI (separate repository, not in this workspace)
10
 - **Frontend**: Vue 2 + Element UI (separate repository, not in this workspace)
11
 - **Database**: MySQL 8 (dual datasource via Druid), TDengine 3.x for time-series
11
 - **Database**: MySQL 8 (dual datasource via Druid), TDengine 3.x for time-series
12
-- **Cache**: Redis (localhost:6379)
12
+- **Cache**: Redis (`${REDIS_HOST:localhost}:6379`)
13
 - **Message Broker**: MQTT (EMQX on 47.104.204.180:1883)
13
 - **Message Broker**: MQTT (EMQX on 47.104.204.180:1883)
14
 
14
 
15
 ## Build & Run Commands
15
 ## Build & Run Commands
55
 ### TDengine (Time-Series Database)
55
 ### TDengine (Time-Series Database)
56
 - Used for high-frequency IoT telemetry ingestion
56
 - Used for high-frequency IoT telemetry ingestion
57
 - Connection pool: HikariCP via `TDengineService`
57
 - Connection pool: HikariCP via `TDengineService`
58
-- Connects to `jdbc:TAOS://127.0.0.1:6031/`
58
+- Connects to `jdbc:TAOS://172.21.185.173:6031/` (host network, via host IP)
59
 - Super-table pattern with column caching (`stableColumnCache`)
59
 - Super-table pattern with column caching (`stableColumnCache`)
60
 
60
 
61
 ### Redis
61
 ### Redis
62
-- Host: `localhost:6379`
62
+- Host: `${REDIS_HOST:localhost}:6379` (production overrides via `REDIS_HOST` env var)
63
 - Key patterns (defined in `common/RedisKeys.java`):
63
 - Key patterns (defined in `common/RedisKeys.java`):
64
   - `DSB:active:devices` — Set of active IoT device keys
64
   - `DSB:active:devices` — Set of active IoT device keys
65
   - `DSB:<controllerId>:<metricName>` — Hash storing device telemetry
65
   - `DSB:<controllerId>:<metricName>` — Hash storing device telemetry
81
 MYSQL_USERNAME=root
81
 MYSQL_USERNAME=root
82
 MYSQL_PASSWORD="..."
82
 MYSQL_PASSWORD="..."
83
 REDIS_PASSWORD=
83
 REDIS_PASSWORD=
84
+REDIS_HOST=172.21.185.173
84
 MQTT_USERNAME=...
85
 MQTT_USERNAME=...
85
 MQTT_PASSWORD=...
86
 MQTT_PASSWORD=...
86
 TDENGINE_USERNAME=root
87
 TDENGINE_USERNAME=root

+ 24
- 0
deploy/.env Просмотреть файл

1
+# IoT 平台环境变量配置
2
+# 启动前自动加载:./start.sh start
3
+# 或手动加载:source .env
4
+
5
+# MySQL 数据库(主从共用)
6
+MYSQL_USERNAME=root
7
+MYSQL_PASSWORD="Zhu059300()__"
8
+
9
+# Redis
10
+REDIS_PASSWORD=
11
+
12
+# MQTT Broker
13
+MQTT_USERNAME=NjniyrEO
14
+MQTT_PASSWORD=2b577892f4824d466dbc323a1ee4dfe1902c55bb
15
+
16
+# TDengine 时序数据库
17
+TDENGINE_USERNAME=root
18
+TDENGINE_PASSWORD=taosdata
19
+
20
+# Druid 监控(生产环境建议设为 false 关闭)
21
+DRUID_STAT_ENABLED=true
22
+DRUID_USERNAME=ruoyi
23
+DRUID_PASSWORD=123456
24
+REDIS_HOST=172.21.185.173

+ 15
- 0
deploy/Dockerfile Просмотреть файл

1
+FROM tdengine/tdengine:3.3.6.13
2
+
3
+ENV DEBIAN_FRONTEND=noninteractive
4
+RUN apt-get update && apt-get install -y --no-install-recommends openjdk-8-jre-headless && rm -rf /var/lib/apt/lists/*
5
+
6
+WORKDIR /opt/iot-platform
7
+
8
+COPY iot-platform.jar .
9
+COPY .env .
10
+COPY start-container.sh .
11
+RUN chmod +x start-container.sh
12
+
13
+EXPOSE 8887
14
+
15
+ENTRYPOINT ["./start-container.sh"]

+ 35
- 26
deploy/README.md Просмотреть файл

2
 
2
 
3
 ## 概述
3
 ## 概述
4
 
4
 
5
-本项目使用 Podman 容器化部署,通过 `deploy.sh` 脚本实现一键构建、上传、构建镜像、健康检查和自动回滚
5
+本项目使用 Podman 容器化部署,通过 `deploy.sh` 脚本实现一键构建、上传、构建镜像、健康检查。
6
 
6
 
7
 ## 服务器环境
7
 ## 服务器环境
8
 
8
 
10
 - **OS**: Alibaba Cloud Linux 3
10
 - **OS**: Alibaba Cloud Linux 3
11
 - **容器运行时**: Podman 4.9.4 (docker 兼容)
11
 - **容器运行时**: Podman 4.9.4 (docker 兼容)
12
 - **Java**: OpenJDK 1.8 (容器内)
12
 - **Java**: OpenJDK 1.8 (容器内)
13
-- **安装目录**: `/opt/iot-platform/`
13
+- **安装目录**: `/mnt/iot-platform/`(兼容软链接 `/opt/iot-platform`
14
 - **容器名称**: `iot-platform`
14
 - **容器名称**: `iot-platform`
15
 
15
 
16
 ## 目录结构
16
 ## 目录结构
17
 
17
 
18
 ```
18
 ```
19
-/opt/iot-platform/
19
+/mnt/iot-platform/
20
 ├── iot-platform.jar          # 当前运行版本
20
 ├── iot-platform.jar          # 当前运行版本
21
 ├── Dockerfile                # 容器镜像构建定义
21
 ├── Dockerfile                # 容器镜像构建定义
22
 ├── start-container.sh        # 容器启动脚本(加载 .env)
22
 ├── start-container.sh        # 容器启动脚本(加载 .env)
29
 └── logs/                     # 日志输出(宿主机挂载)
29
 └── logs/                     # 日志输出(宿主机挂载)
30
 ```
30
 ```
31
 
31
 
32
+## 容器架构
33
+
34
+| 容器 | 网络模式 | 端口映射 | 数据持久化 |
35
+|------|---------|---------|-----------|
36
+| `tdengine-operator` | host | 6031, 6041 | `/mnt/tdengine-operator/data` |
37
+| `taos-explorer` | bridge | `127.0.0.1:6060->6060` | `/mnt/taos-explorer-data` |
38
+| `iot-platform` | bridge | `0.0.0.0:8887->8887` | `/mnt/iot-platform/logs` |
39
+
32
 ## 快速开始
40
 ## 快速开始
33
 
41
 
34
 ### 首次部署(服务器初始化)
42
 ### 首次部署(服务器初始化)
35
 
43
 
36
-确保服务器上已有 `/opt/iot-platform/` 目录,并包含 `Dockerfile`、`.env`、`start-container.sh`:
44
+确保服务器上已有 `/mnt/iot-platform/` 目录,并包含 `Dockerfile`、`.env`、`start-container.sh`:
37
 
45
 
38
 ```bash
46
 ```bash
39
 # 从本地同步必要文件到服务器
47
 # 从本地同步必要文件到服务器
40
-scp deploy/setup-server.sh root@47.104.204.180:/opt/iot-platform/
41
-scp .env root@47.104.204.180:/opt/iot-platform/
42
-scp Dockerfile root@47.104.204.180:/opt/iot-platform/
43
-scp start-container.sh root@47.104.204.180:/opt/iot-platform/
44
-ssh root@47.104.204.180 "chmod +x /opt/iot-platform/start-container.sh"
48
+scp deploy/setup-server.sh root@47.104.204.180:/mnt/iot-platform/
49
+scp .env root@47.104.204.180:/mnt/iot-platform/
50
+scp deploy/Dockerfile root@47.104.204.180:/mnt/iot-platform/
51
+scp deploy/start-container.sh root@47.104.204.180:/mnt/iot-platform/
52
+ssh root@47.104.204.180 "chmod +x /mnt/iot-platform/start-container.sh"
45
 ```
53
 ```
46
 
54
 
47
 然后执行首次部署:
55
 然后执行首次部署:
62
 
70
 
63
 流程:
71
 流程:
64
 1. 本地执行 `mvn clean package`
72
 1. 本地执行 `mvn clean package`
65
-2. 上传 jar 到服务器
73
+2. 上传 jar 到服务器 `/mnt/iot-platform/`
66
 3. 备份当前版本(带时间戳)
74
 3. 备份当前版本(带时间戳)
67
 4. 停止并删除旧容器
75
 4. 停止并删除旧容器
68
 5. 替换 jar 并构建新镜像
76
 5. 替换 jar 并构建新镜像
69
-6. 启动容器(host 网络模式
77
+6. 启动容器(bridge 网络模式,端口映射 `8887:8887`
70
 7. 健康检查(`/actuator/health`)
78
 7. 健康检查(`/actuator/health`)
71
-8. 失败则自动回滚到上一个版本
79
+8. 失败时保留容器用于排查(不再自动回滚)
72
 
80
 
73
 #### 方式二:指定 jar 部署
81
 #### 方式二:指定 jar 部署
74
 
82
 
86
 
94
 
87
 ## 环境变量
95
 ## 环境变量
88
 
96
 
89
-`.env` 文件位于项目根目录和服务器 `/opt/iot-platform/.env`,包含所有敏感凭据
97
+`.env` 文件位于项目根目录和服务器 `/mnt/iot-platform/.env`,包含所有环境变量
90
 
98
 
91
 ```bash
99
 ```bash
92
 # MySQL
100
 # MySQL
95
 
103
 
96
 # Redis
104
 # Redis
97
 REDIS_PASSWORD=
105
 REDIS_PASSWORD=
106
+REDIS_HOST=172.21.185.173
98
 
107
 
99
 # MQTT
108
 # MQTT
100
 MQTT_USERNAME=...
109
 MQTT_USERNAME=...
110
 DRUID_PASSWORD=...
119
 DRUID_PASSWORD=...
111
 ```
120
 ```
112
 
121
 
113
-**注意**:`.env` 文件已加入 `.gitignore`,**切勿提交到 Git**。
114
-
115
 ## 服务器管理
122
 ## 服务器管理
116
 
123
 
117
 ```bash
124
 ```bash
118
 # 查看容器状态
125
 # 查看容器状态
119
-podman ps | grep iot-platform
126
+podman ps
120
 
127
 
121
 # 查看实时日志
128
 # 查看实时日志
122
 podman logs -f iot-platform
129
 podman logs -f iot-platform
125
 podman logs --tail 100 iot-platform
132
 podman logs --tail 100 iot-platform
126
 
133
 
127
 # 查看宿主机上的日志文件(和容器内同步)
134
 # 查看宿主机上的日志文件(和容器内同步)
128
-tail -f /opt/iot-platform/logs/iot-platform.$(date +%Y-%m-%d).*.log
135
+tail -f /mnt/iot-platform/logs/iot-platform.$(date +%Y-%m-%d).*.log
129
 
136
 
130
 # 停止容器
137
 # 停止容器
131
 podman stop iot-platform
138
 podman stop iot-platform
141
 
148
 
142
 ```bash
149
 ```bash
143
 # 在服务器上执行
150
 # 在服务器上执行
144
-bash /opt/iot-platform/bin/health-check.sh localhost 8887 30
151
+bash /mnt/iot-platform/bin/health-check.sh localhost 8887 30
145
 
152
 
146
 # 或从本地检查
153
 # 或从本地检查
147
 curl -s http://47.104.204.180:8887/actuator/health
154
 curl -s http://47.104.204.180:8887/actuator/health
153
 
160
 
154
 ## 回滚
161
 ## 回滚
155
 
162
 
156
-如果部署失败,`deploy.sh` 会自动回滚到上一个版本。
157
-
158
-手动回滚:
163
+部署失败时容器会保留用于排查,需手动回滚:
159
 
164
 
160
 ```bash
165
 ```bash
161
 ssh root@47.104.204.180
166
 ssh root@47.104.204.180
164
 podman stop iot-platform && podman rm iot-platform
169
 podman stop iot-platform && podman rm iot-platform
165
 
170
 
166
 # 恢复旧版本 jar
171
 # 恢复旧版本 jar
167
-ls /opt/iot-platform/backup/          # 查看可用备份
168
-cp /opt/iot-platform/backup/iot-platform-XXXX.jar /opt/iot-platform/iot-platform.jar
172
+ls /mnt/iot-platform/backup/          # 查看可用备份
173
+cp /mnt/iot-platform/backup/iot-platform-XXXX.jar /mnt/iot-platform/iot-platform.jar
169
 
174
 
170
 # 重新构建镜像并启动
175
 # 重新构建镜像并启动
171
-cd /opt/iot-platform && podman build -t iot-platform:latest .
176
+cd /mnt/iot-platform && podman build -t iot-platform:latest .
172
 podman run -d \
177
 podman run -d \
173
     --name iot-platform \
178
     --name iot-platform \
174
-    --network host \
179
+    --network bridge \
175
     --restart unless-stopped \
180
     --restart unless-stopped \
176
-    -v /opt/iot-platform/logs:/opt/iot-platform/logs \
181
+    -p 8887:8887 \
182
+    -v /mnt/iot-platform/logs:/opt/iot-platform/logs \
177
     localhost/iot-platform:latest
183
     localhost/iot-platform:latest
178
 ```
184
 ```
179
 
185
 
187
 | `Dockerfile` | 容器镜像构建定义 |
193
 | `Dockerfile` | 容器镜像构建定义 |
188
 | `start-container.sh` | 容器内启动脚本(加载 .env) |
194
 | `start-container.sh` | 容器内启动脚本(加载 .env) |
189
 | `iot-platform.service` | systemd 服务定义(已废弃,保留备用) |
195
 | `iot-platform.service` | systemd 服务定义(已废弃,保留备用) |
196
+| `config/taos.cfg` | TDengine 服务端配置 |
197
+| `config/explorer.toml` | taos-explorer 配置 |
198
+| `config/tdengine-explorer.conf` | nginx 代理配置 |

+ 117
- 0
deploy/config/explorer.toml Просмотреть файл

1
+# This is a automacically generated configuration file for Explorer in [TOML](https://toml.io/) format.
2
+#
3
+# Here is a full list of available options.
4
+
5
+# Explorer server port to listen on.
6
+# Default is 6060.
7
+#
8
+port = 6060
9
+
10
+# IPv4 listen address.
11
+# Default is 0.0.0.0
12
+addr = "0.0.0.0"
13
+
14
+# IPv6 listen address.
15
+
16
+# ipv6 = "::1"
17
+
18
+# explorer server instance id
19
+# 
20
+# The instanceId of each instance is unique on the host
21
+# instanceId = 1
22
+
23
+# Explorer server log level.
24
+# Default is "info"
25
+# 
26
+# Deprecated: use log.level instead
27
+log_level = "info"
28
+
29
+# All data files are stored in this directory
30
+# data_dir = "/var/lib/taos/explorer" # Default for Linux
31
+# data_dir = "C:\\TDengine\\data\\explorer" # Default for Windows
32
+
33
+# REST API endpoint to connect to the cluster.
34
+# This configuration is also the target for data migration tasks.
35
+# 
36
+# Default is "http://localhost:6041" - the default endpoint for REST API.
37
+#
38
+cluster = "http://172.21.185.173:6041"
39
+
40
+# native endpoint to connect to the cluster.
41
+# Default is disabled. To enable it, set it to the native API URL like "taos://localhost:6030" and uncomment it.
42
+# If you enable it, you will get more performance for data migration tasks.
43
+# If modify this config item, you must relogin to clear the cache in browser local storage.
44
+#
45
+# cluster_native = "taos://localhost:6030"
46
+
47
+# API endpoint for data replication/backup/data sources. No default option.
48
+#   Set it to API URL like "http://localhost:6050".
49
+#
50
+x_api = "http://localhost:6050"
51
+
52
+# Please set it to same as the "grpc" parameter used by taosX Service; 
53
+# If "grpc" parameter is not set explicitly in taosX service, please set it to the default grpc address of taosX
54
+grpc = "http://localhost:6055"
55
+
56
+# CORS configuration switch, it allows cross-origin access
57
+cors = true
58
+
59
+# cloud open api.
60
+# cloud_open_api = "https://pre.ali.cloud.taosdata.com/openapi"
61
+
62
+# Enable ssl
63
+# If the following two files exist, enable ssl protocol
64
+#
65
+[ssl]
66
+
67
+# SSL certificate
68
+#
69
+# certificate = "/path/to/ca.file" # on linux/macOS
70
+# certificate = "C:\\path\\to\\ca.file" # on windows
71
+
72
+# SSL certificate key
73
+#
74
+# certificate_key = "/path/to/key.file" # on linux/macOS
75
+# certificate_key = "C:\\path\\to\\key.file" # on windows
76
+
77
+# log configuration
78
+[log]
79
+# All log files are stored in this directory
80
+# 
81
+# path = "/var/log/taos" # on linux/macOS
82
+# path = "C:\\TDengine\\log" # on windows
83
+
84
+# log filter level
85
+#
86
+# level = "info"
87
+
88
+# Compress archived log files or not
89
+# 
90
+# compress = false
91
+
92
+# The number of log files retained by the current explorer server instance in the `path` directory
93
+# 
94
+# rotationCount = 30
95
+
96
+# Rotate when the log file reaches this size
97
+# 
98
+# rotationSize = "1GB"
99
+
100
+# Log downgrade when the remaining disk space reaches this size, only logging `ERROR` level logs
101
+# 
102
+# reservedDiskSize = "1GB"
103
+
104
+# The number of days log files are retained
105
+#
106
+# keepDays = 30
107
+
108
+# integrated with Grafana
109
+[grafana]
110
+# The token of the Grafana server, which is used to access the Grafana server.
111
+#token = ""
112
+
113
+# The URL of the Grafana dashboard, which is used to display the monitoring data of the TDengine cluster.
114
+# You can configure multiple Grafana dashboards.
115
+[grafana.dashboards]
116
+#TDengine3 = "http://localhost:3000/d/000000001/tdengine3"
117
+#taosX = "http://localhost:3000/d/000000002/taosx"

+ 2
- 0
deploy/config/taos.cfg Просмотреть файл

1
+fqdn 172.21.185.173
2
+serverPort 6031

+ 16
- 0
deploy/config/tdengine-explorer.conf Просмотреть файл

1
+server {
2
+    listen 172.21.185.173:6060;
3
+    server_name 47.104.204.180;
4
+
5
+    location / {
6
+        proxy_pass http://127.0.0.1:6060;
7
+        proxy_set_header Host $host;
8
+        proxy_set_header X-Real-IP $remote_addr;
9
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
10
+        proxy_read_timeout 300s;
11
+
12
+        proxy_http_version 1.1;
13
+        proxy_set_header Upgrade $http_upgrade;
14
+        proxy_set_header Connection "upgrade";
15
+    }
16
+}

+ 18
- 38
deploy/deploy.sh Просмотреть файл

15
 # 配置
15
 # 配置
16
 SERVER_HOST="${SERVER_HOST:-47.104.204.180}"
16
 SERVER_HOST="${SERVER_HOST:-47.104.204.180}"
17
 SERVER_USER="${SERVER_USER:-root}"
17
 SERVER_USER="${SERVER_USER:-root}"
18
-REMOTE_DIR="${REMOTE_DIR:-/opt/iot-platform}"
18
+REMOTE_DIR="${REMOTE_DIR:-/mnt/iot-platform}"
19
 APP_NAME="iot-platform"
19
 APP_NAME="iot-platform"
20
 LOCAL_JAR=""
20
 LOCAL_JAR=""
21
 DO_BUILD=false
21
 DO_BUILD=false
101
         cp -a ${REMOTE_DIR}/${APP_NAME}.jar ${REMOTE_DIR}/backup/${BACKUP_NAME}
101
         cp -a ${REMOTE_DIR}/${APP_NAME}.jar ${REMOTE_DIR}/backup/${BACKUP_NAME}
102
         echo '  已备份: ${BACKUP_NAME}'
102
         echo '  已备份: ${BACKUP_NAME}'
103
         ls -lh ${REMOTE_DIR}/backup/ | tail -5
103
         ls -lh ${REMOTE_DIR}/backup/ | tail -5
104
+        # 清理旧备份,保留最近 10 个
105
+        BACKUP_COUNT=\$(ls -1t ${REMOTE_DIR}/backup/${APP_NAME}-*.jar 2>/dev/null | wc -l)
106
+        if [ "\$BACKUP_COUNT" -gt 10 ]; then
107
+            ls -1t ${REMOTE_DIR}/backup/${APP_NAME}-*.jar | tail -n +11 | xargs -r rm -f
108
+            echo "  已清理旧备份,保留最近 10 个"
109
+        fi
104
     else
110
     else
105
         echo '  无现有版本,跳过备份'
111
         echo '  无现有版本,跳过备份'
106
     fi
112
     fi
124
     echo '  构建镜像...'
130
     echo '  构建镜像...'
125
     cd ${REMOTE_DIR} && podman build -t ${APP_NAME}:latest . >/dev/null 2>&1
131
     cd ${REMOTE_DIR} && podman build -t ${APP_NAME}:latest . >/dev/null 2>&1
126
     echo '  镜像构建完成'
132
     echo '  镜像构建完成'
133
+    podman image prune -f >/dev/null 2>&1
134
+    echo '  已清理 dangling 镜像'
127
     podman images | grep ${APP_NAME}
135
     podman images | grep ${APP_NAME}
128
 "
136
 "
129
 echo -e "${GREEN}[4/6] 镜像构建完成${NC}"
137
 echo -e "${GREEN}[4/6] 镜像构建完成${NC}"
133
 ssh "${SERVER_USER}@${SERVER_HOST}" "
141
 ssh "${SERVER_USER}@${SERVER_HOST}" "
134
     podman run -d \
142
     podman run -d \
135
         --name ${APP_NAME} \
143
         --name ${APP_NAME} \
136
-        --network host \
144
+        --network bridge \
137
         --restart unless-stopped \
145
         --restart unless-stopped \
138
-        -v ${REMOTE_DIR}/logs:/opt/iot-platform/logs \
146
+        -p 8887:8887 \
147
+        -v /mnt/iot-platform/logs:/opt/iot-platform/logs \
139
         localhost/${APP_NAME}:latest >/dev/null 2>&1
148
         localhost/${APP_NAME}:latest >/dev/null 2>&1
140
 "
149
 "
141
 sleep 5
150
 sleep 5
163
     exit 0
172
     exit 0
164
 else
173
 else
165
     echo -e "${RED}[6/6] 健康检查失败!${NC}"
174
     echo -e "${RED}[6/6] 健康检查失败!${NC}"
166
-
167
-    if [ "$NO_ROLLBACK" = true ]; then
168
-        echo -e "${YELLOW}[no-rollback] 已启用不回滚模式,保留新版本用于排查${NC}"
169
-        echo -e "${YELLOW}排查命令:${NC}"
170
-        echo "  ssh ${SERVER_USER}@${SERVER_HOST} 'podman logs -f ${APP_NAME}'"
171
-        echo "  ssh ${SERVER_USER}@${SERVER_HOST} 'podman ps | grep ${APP_NAME}'"
172
-        exit 1
173
-    fi
174
-
175
-    echo -e "${YELLOW}[rollback] 执行回滚...${NC}"
176
-    ssh "${SERVER_USER}@${SERVER_HOST}" "podman stop ${APP_NAME} >/dev/null 2>&1 && podman rm ${APP_NAME} >/dev/null 2>&1 || true"
177
-
178
-    if ssh "${SERVER_USER}@${SERVER_HOST}" "test -f ${REMOTE_DIR}/backup/${BACKUP_NAME}"; then
179
-        echo -e "${YELLOW}[rollback] 恢复旧版本...${NC}"
180
-        ssh "${SERVER_USER}@${SERVER_HOST}" "
181
-            cp ${REMOTE_DIR}/backup/${BACKUP_NAME} ${REMOTE_DIR}/${APP_NAME}.jar
182
-            cd ${REMOTE_DIR} && podman build -t ${APP_NAME}:latest . >/dev/null 2>&1
183
-            podman run -d \
184
-                --name ${APP_NAME} \
185
-                --network host \
186
-                --restart unless-stopped \
187
-                -v ${REMOTE_DIR}/logs:/opt/iot-platform/logs \
188
-                localhost/${APP_NAME}:latest >/dev/null 2>&1
189
-        "
190
-        sleep 5
191
-
192
-        if ssh "${SERVER_USER}@${SERVER_HOST}" "bash ${REMOTE_DIR}/bin/health-check.sh localhost 8887 60"; then
193
-            echo -e "${GREEN}[rollback] 回滚成功,旧版本已恢复${NC}"
194
-        else
195
-            echo -e "${RED}[rollback] 回滚后健康检查仍失败,请手动排查!${NC}"
196
-        fi
197
-    else
198
-        echo -e "${RED}[rollback] 无备份可回滚,请手动修复!${NC}"
199
-    fi
200
-
175
+    echo -e "${YELLOW}保留容器用于排查,不自动回滚${NC}"
176
+    echo ""
177
+    echo "排查命令:"
178
+    echo "  ssh ${SERVER_USER}@${SERVER_HOST} 'podman logs -f ${APP_NAME}'"
179
+    echo "  ssh ${SERVER_USER}@${SERVER_HOST} 'podman ps | grep ${APP_NAME}'"
180
+    echo "  ssh ${SERVER_USER}@${SERVER_HOST} 'curl -s http://localhost:8887/actuator/health'"
201
     exit 1
181
     exit 1
202
 fi
182
 fi

+ 7
- 0
deploy/start-container.sh Просмотреть файл

1
+#!/bin/bash
2
+set -a
3
+source /opt/iot-platform/.env
4
+set +a
5
+
6
+cd /opt/iot-platform
7
+exec java -server -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Xms1g -Xmx2g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/iot-platform/logs/heapdump.hprof -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar iot-platform.jar --spring.profiles.include=online

+ 1
- 1
iot-platform/src/main/java/com/iot/platform/config/IotProperties.java Просмотреть файл

115
      * TDengine配置
115
      * TDengine配置
116
      */
116
      */
117
     public static class TDengine {
117
     public static class TDengine {
118
-        private String url = "jdbc:TAOS://127.0.0.1:6031/";
118
+        private String url = "jdbc:TAOS://172.21.185.173:6031/";
119
         private String username = "";
119
         private String username = "";
120
         private String password = "";
120
         private String password = "";
121
 
121
 

+ 1
- 7
iot-platform/src/main/java/com/iot/platform/mqtt/MqttChargeStationConsumer.java Просмотреть файл

77
                            //deviceid_controllerid
77
                            //deviceid_controllerid
78
         String tableName = superTable + "_" +dbName;
78
         String tableName = superTable + "_" +dbName;
79
 
79
 
80
-        //拆分出数据库名称
81
-
82
-        //拆分出对于看哪个的超级表的名称
83
-
84
-        //字表名称公式
85
-
86
 //        int i = ((int) "g".getBytes("UTF-8")[0]) % 10;
80
 //        int i = ((int) "g".getBytes("UTF-8")[0]) % 10;
87
 
81
 
88
         tdengineService.insertBatch(dbNamePrefix, superTable,tableName, batchToInsert);
82
         tdengineService.insertBatch(dbNamePrefix, superTable,tableName, batchToInsert);
89
     }
83
     }
90
-}
84
+}

+ 72
- 19
iot-platform/src/main/java/com/iot/platform/service/TdEngineService.java Просмотреть файл

47
     // 每批次最大列数(防止 TDengine 行超限)
47
     // 每批次最大列数(防止 TDengine 行超限)
48
     private static final int MAX_COLUMNS_PER_INSERT = 100;
48
     private static final int MAX_COLUMNS_PER_INSERT = 100;
49
 
49
 
50
+    // 拆分超级表的列数阈值
51
+    private static final int SPLIT_STABLE_COLUMN_THRESHOLD = 100;
52
+
50
     // 默认 VARCHAR 字段长度限制
53
     // 默认 VARCHAR 字段长度限制
51
     private static final int DEFAULT_VARCHAR_LENGTH = 128;
54
     private static final int DEFAULT_VARCHAR_LENGTH = 128;
52
 
55
 
136
         return name != null && name.matches(ALLOWED_TABLE_NAME);
139
         return name != null && name.matches(ALLOWED_TABLE_NAME);
137
     }
140
     }
138
 
141
 
139
-    private boolean isNumeric(String str) {
140
-        if (str == null || str.isEmpty()) {
141
-            return false;
142
-        }
143
-        try {
144
-            Double.parseDouble(str);
145
-            return true;
146
-        } catch (NumberFormatException e) {
147
-            return false;
148
-        }
149
-    }
150
-
151
     // === 缓存工具方法 ===
142
     // === 缓存工具方法 ===
152
     private String getStableKey(String dbName, String stableName) {
143
     private String getStableKey(String dbName, String stableName) {
153
         return dbName + "." + stableName;
144
         return dbName + "." + stableName;
278
     // ==========================================
269
     // ==========================================
279
     // 批量插入(按列存储)
270
     // 批量插入(按列存储)
280
     // ==========================================
271
     // ==========================================
281
-    public void insertBatch(String dbName, String superTable,String table, List<Map<String, Object>> dataList)
272
+    public void insertBatch(String dbName, String superTable, String table, List<Map<String, Object>> dataList)
282
             throws SQLException {
273
             throws SQLException {
283
 
274
 
284
         if (dataList == null || dataList.isEmpty()) {
275
         if (dataList == null || dataList.isEmpty()) {
286
             return;
277
             return;
287
         }
278
         }
288
 
279
 
289
-//        String superTableName = extractSuperTableName(table);
280
+        Map<String, String> columnTypes = collectColumnTypes(dataList);
290
 
281
 
291
-        int batchSize = DEFAULT_BATCH_SIZE;
292
-        for (int i = 0; i < dataList.size(); i += batchSize) {
293
-            List<Map<String, Object>> batch = dataList.subList(i, Math.min(i + batchSize, dataList.size()));
294
-            insertBatchInternal(dbName, superTable, table, batch);
282
+        // 所有数据都走拆分逻辑:按首字符 UTF-8 值模 10 分组到不同的超级表
283
+        splitAndInsertToMultipleStables(dbName, superTable, table, dataList, columnTypes);
284
+
285
+        log.info("批量写入完成: {} | 条数: {}", table, dataList.size());
286
+    }
287
+
288
+    /**
289
+     * 根据列 key 首字符的 UTF-8 值拆分到多个超级表
290
+     */
291
+    private void splitAndInsertToMultipleStables(String dbName, String baseSuperTable, String baseTable,
292
+                                                 List<Map<String, Object>> dataList,
293
+                                                 Map<String, String> columnTypes) throws SQLException {
294
+        // 按首字符 UTF-8 值分组
295
+        Map<Integer, Map<String, String>> groupColumnTypes = new LinkedHashMap<>();
296
+        for (Map.Entry<String, String> entry : columnTypes.entrySet()) {
297
+            int groupId = getFirstCharGroupId(entry.getKey());
298
+            groupColumnTypes.computeIfAbsent(groupId, k -> new LinkedHashMap<>()).put(entry.getKey(), entry.getValue());
299
+        }
300
+
301
+        log.info("拆分后超级表数量: {} | 分组: {}", groupColumnTypes.size(), groupColumnTypes.keySet());
302
+
303
+        // 每个分组作为一个超级表,处理其对应的列和数据
304
+        for (Map.Entry<Integer, Map<String, String>> group : groupColumnTypes.entrySet()) {
305
+            int groupId = group.getKey();
306
+            String stableName = "device_" + groupId;
307
+            String tableName = baseTable + "_" + groupId;
308
+
309
+            // 筛选出该组列的数据
310
+            List<Map<String, Object>> filteredData = filterDataByColumnGroup(dataList, group.getValue().keySet());
311
+
312
+            // 按普通逻辑插入
313
+            int batchSize = DEFAULT_BATCH_SIZE;
314
+            for (int i = 0; i < filteredData.size(); i += batchSize) {
315
+                List<Map<String, Object>> batch = filteredData.subList(i, Math.min(i + batchSize, filteredData.size()));
316
+                insertBatchInternal(dbName, stableName, tableName, batch);
317
+            }
318
+            log.info("分组插入完成: stable={}, table={}, 列数={}, 数据条数={}",
319
+                    stableName, tableName, group.getValue().size(), filteredData.size());
295
         }
320
         }
321
+    }
322
+
323
+    /**
324
+     * 获取列名首字符的 UTF-8 值模 10,作为分组 ID
325
+     */
326
+    private int getFirstCharGroupId(String columnName) {
327
+        if (columnName == null || columnName.isEmpty()) {
328
+            return 0;
329
+        }
330
+        String firstChar = columnName.substring(0, 1);
331
+        byte[] bytes = firstChar.getBytes(java.nio.charset.StandardCharsets.UTF_8);
332
+        return (int) bytes[0] % 10;
333
+    }
296
 
334
 
297
-        log.info("批量写入成功: {} | 条数: {}", table, dataList.size());
335
+    /**
336
+     * 筛选出指定列的数据
337
+     */
338
+    private List<Map<String, Object>> filterDataByColumnGroup(List<Map<String, Object>> dataList, Set<String> columns) {
339
+        List<Map<String, Object>> filtered = new ArrayList<>();
340
+        for (Map<String, Object> data : dataList) {
341
+            if (data == null) continue;
342
+            Map<String, Object> filteredRow = new LinkedHashMap<>();
343
+            for (String col : columns) {
344
+                if (data.containsKey(col)) {
345
+                    filteredRow.put(col, data.get(col));
346
+                }
347
+            }
348
+            filtered.add(filteredRow);
349
+        }
350
+        return filtered;
298
     }
351
     }
299
 
352
 
300
     private String extractSuperTableName(String table) {
353
     private String extractSuperTableName(String table) {

+ 2
- 2
iot-platform/src/main/resources/application.yml Просмотреть файл

29
     restart:
29
     restart:
30
       enabled: false
30
       enabled: false
31
   redis:
31
   redis:
32
-    host: localhost
32
+    host: ${REDIS_HOST:localhost}
33
     port: 6379
33
     port: 6379
34
     database: 0
34
     database: 0
35
     password: ${REDIS_PASSWORD:}
35
     password: ${REDIS_PASSWORD:}
70
     password: ${MQTT_PASSWORD:}
70
     password: ${MQTT_PASSWORD:}
71
     charge-station-topic: ${MQTT_CHARGE_STATION_TOPIC:station/ChargeStation/device/+/post/json}
71
     charge-station-topic: ${MQTT_CHARGE_STATION_TOPIC:station/ChargeStation/device/+/post/json}
72
   tdengine:
72
   tdengine:
73
-    url: jdbc:TAOS://127.0.0.1:6031/
73
+    url: jdbc:TAOS://172.21.185.173:6031/
74
     username: ${TDENGINE_USERNAME:}
74
     username: ${TDENGINE_USERNAME:}
75
     password: ${TDENGINE_PASSWORD:}
75
     password: ${TDENGINE_PASSWORD:}

Загрузка…
Отмена
Сохранить