diff --git a/README.md b/README.md index 88fbf15..4614ccc 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ tag 格式统一为 **`<服务名>-<版本>`**。 | 类别 | 服务 | | --- | --- | -| 基础组件 | MySQL、Redis、RabbitMQ、Consul | +| 基础组件 | MySQL、Redis、RabbitMQ、Consul、TDengine(流量历史) | | 业务服务 | admin-svr(管理端后端)、admin-ui(管理端前端)、openapi(对外 API)、app-server(小程序后端)、app-web(小程序前端)、upstream-chan(运营商对接)、upstream-proxy(通道缓存转发)、worker-svr(异步任务)、sms(短信)、docs(文档站) | | 可选 | onelink-mock(运营商接口挡板)、bot(飞书通知)、nginx(反向代理) | @@ -82,13 +82,23 @@ iotlcm-deploy/ │ ├── upstream-chan/config.yaml │ ├── upstream-proxy/config.yaml │ └── worker-svr/config.yaml -├── schema/init.sql # 数据库初始化(单文件,首次启动自动执行) +├── schema/init.sql # MySQL 初始化(单文件,首次启动自动执行) +├── tdengine-init/ # TDengine 建库建表(首次启动自动执行) └── data/ # 运行数据(首次启动自动创建,不入版本库) ``` 全新初始化生成 95 张业务表(原有 91 张 + worker 异步恢复所需 4 张);已部署旧库不会自动执行新增迁移,见 `schema/README.md`。 -本编排不包含 TDengine。默认配置中的时序库名称留空,因此首页流量/用量图表等依赖时序数据的功能不能仅靠上述容器完整展示;需要自行部署 TDengine,并在相关服务的 `config.yaml` 中配置连接及数据库名称。管理端登录、菜单、卡信息筛选不依赖这项配置。 +编排已内置 TDengine(流量历史库),由 `tdengine-init` 服务在 TDengine 健康后自动建库建表: + +| 库 | 分片 | 保留 | 超级表 | +| --- | --- | --- | --- | +| `iotlcm_daily` | 7 天 | 3 年 | `t_sim_card_usage_daily` | +| `iotlcm_monthly` | 30 天 | 3 年 | `t_sim_card_usage_monthly` | + +结构取自生产环境,`tdengine-init` 会校验建表结果,缺表则报错退出。业务代码只读写不建表,因此这一步不能跳过。 + +TDengine 版本为 `3.3.6.13`(公开镜像可得),生产用的是 `3.4.0.9`。两者在流量历史的读写上一致;如需与生产完全对齐,可自行构建镜像并替换 `docker-compose.yml` 里的 tag。 ## 上线前必改 diff --git a/config/admin-svr/config.yaml b/config/admin-svr/config.yaml index 784aea4..1b5cbeb 100644 --- a/config/admin-svr/config.yaml +++ b/config/admin-svr/config.yaml @@ -179,10 +179,13 @@ tsdb: database: "TdEngine" tdengine: type: "taosWS" - dsn: ":@ws(:)/" - dbName: "" - dailyDbName: "" - monthlyDbName: "" + # TDengine 3.x WebSocket 地址(容器内服务名 tdengine,端口 6041) + # 口令为 TDengine 默认 root/taosdata;改过口令需同步改这里 + dsn: "root:taosdata@ws(tdengine:6041)/" + # dbName 只是连接池键名与兜底值,不决定实际访问哪个库 + dbName: "iotlcm_daily" + dailyDbName: "iotlcm_daily" + monthlyDbName: "iotlcm_monthly" maxOpenConns: 100 maxIdleConns: 10 diff --git a/config/app-server/config.yaml b/config/app-server/config.yaml index 0ac7726..231ea0e 100644 --- a/config/app-server/config.yaml +++ b/config/app-server/config.yaml @@ -197,10 +197,13 @@ tsdb: database: "TdEngine" tdengine: type: "taosWS" - dsn: ":@ws(:)/" - dbName: "" - dailyDbName: "" - monthlyDbName: "" + # TDengine 3.x WebSocket 地址(容器内服务名 tdengine,端口 6041) + # 口令为 TDengine 默认 root/taosdata;改过口令需同步改这里 + dsn: "root:taosdata@ws(tdengine:6041)/" + # dbName 只是连接池键名与兜底值,不决定实际访问哪个库 + dbName: "iotlcm_daily" + dailyDbName: "iotlcm_daily" + monthlyDbName: "iotlcm_monthly" maxOpenConns: 100 maxIdleConns: 10 diff --git a/config/worker-svr/config.yaml b/config/worker-svr/config.yaml index 43f5ca7..b7958f7 100644 --- a/config/worker-svr/config.yaml +++ b/config/worker-svr/config.yaml @@ -121,11 +121,13 @@ tsdb: database: "TdEngine" tdengine: type: "taosWS" - dsn: ":@ws(:)/" + # TDengine 3.x WebSocket 地址(容器内服务名 tdengine,端口 6041) + # 口令为 TDengine 默认 root/taosdata;改过口令需同步改这里 + dsn: "root:taosdata@ws(tdengine:6041)/" # dbName 只是连接池键名与兜底值,不决定实际访问哪个库 - dbName: "" - dailyDbName: "" - monthlyDbName: "" + dbName: "iotlcm_daily" + dailyDbName: "iotlcm_daily" + monthlyDbName: "iotlcm_monthly" maxOpenConns: 100 maxIdleConns: 10 diff --git a/docker-compose.yml b/docker-compose.yml index 22a7831..9d8d6cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -91,6 +91,42 @@ services: retries: 5 start_period: 30s + # 时序数据库:存流量历史(daily / monthly),worker-svr 全量同步写这里 + tdengine: + image: 'crpi-qflrqrxoohlkkvgc.cn-chengdu.personal.cr.aliyuncs.com/iotlcm/iotlcm:tdengine-3.3.6.13' + container_name: iotlcm-tdengine + restart: always + ports: + - "${TDENGINE_PORT:-6041}:6041" # taosAdapter(WebSocket,应用用这个) + - "${TDENGINE_NATIVE_PORT:-6030}:6030" + environment: + TZ: '${TZ:-Asia/Shanghai}' + volumes: + - ./data/tdengine/data:/var/lib/taos + - ./data/tdengine/log:/var/log/taos + networks: + - iotlcm-network + healthcheck: + test: ["CMD-SHELL", "curl -sf http://127.0.0.1:6041/-/ping || exit 1"] + interval: 15s + timeout: 5s + retries: 10 + start_period: 30s + + # 建库建表:业务代码不负责建 TDengine 库表,由本服务在 tdengine 健康后执行一次 + tdengine-init: + image: 'crpi-qflrqrxoohlkkvgc.cn-chengdu.personal.cr.aliyuncs.com/iotlcm/iotlcm:tdengine-3.3.6.13' + container_name: iotlcm-tdengine-init + restart: "no" + entrypoint: ["sh", "/init/run.sh"] + volumes: + - ./tdengine-init:/init:ro + depends_on: + tdengine: + condition: service_healthy + networks: + - iotlcm-network + consul: image: 'crpi-qflrqrxoohlkkvgc.cn-chengdu.personal.cr.aliyuncs.com/iotlcm/iotlcm:consul-1.22' container_name: iotlcm-consul diff --git a/tdengine-init/init.sql b/tdengine-init/init.sql new file mode 100644 index 0000000..a4033e3 --- /dev/null +++ b/tdengine-init/init.sql @@ -0,0 +1,17 @@ +-- ============================================================ +-- TDengine 初始化:流量历史库与超级表 +-- +-- 结构取自生产环境(SHOW CREATE STABLE 导出),仅结构、无数据。 +-- 由 compose 的 tdengine-init 服务在 tdengine 健康后执行。 +-- +-- 注意:`taos -f` 是**按行**执行语句的,每条 SQL 必须写在一行内, +-- 跨行会被判为 "Incomplete SQL statement"。改本文件时务必保持单行。 +-- +-- 业务代码(worker-svr / admin-svr)只连接读写,不负责建库建表, +-- 因此这一步必须在首次启动时完成,否则单卡流量重试写入会报库表不存在。 +-- ============================================================ + +CREATE DATABASE IF NOT EXISTS iotlcm_daily PRECISION 'ms' KEEP 1095d DURATION 7d; +CREATE DATABASE IF NOT EXISTS iotlcm_monthly PRECISION 'ms' KEEP 1095d DURATION 30d; +CREATE STABLE IF NOT EXISTS iotlcm_daily.t_sim_card_usage_daily (ts TIMESTAMP, usage_mb DOUBLE, package_code VARCHAR(128)) TAGS (iccid VARCHAR(64)); +CREATE STABLE IF NOT EXISTS iotlcm_monthly.t_sim_card_usage_monthly (ts TIMESTAMP, usage_mb DOUBLE, package_code VARCHAR(128)) TAGS (iccid VARCHAR(64)); diff --git a/tdengine-init/run.sh b/tdengine-init/run.sh new file mode 100644 index 0000000..d20aadf --- /dev/null +++ b/tdengine-init/run.sh @@ -0,0 +1,39 @@ +#!/bin/sh +# ============================================================ +# TDengine 初始化执行脚本 +# +# 由 compose 的 tdengine-init 服务调用,在 tdengine 健康后执行一次。 +# 做三件事: +# 1. 过滤掉 init.sql 里的注释与空行(taos -f 会逐行执行, +# 注释行会被当成不完整 SQL 报错) +# 2. 执行建库建表 +# 3. 校验超级表确实存在,缺表则以非零码退出(让 compose 暴露失败) +# ============================================================ +set -eu + +echo "[tdengine-init] 准备 SQL(过滤注释与空行)" +grep -vE '^[[:space:]]*(--|$)' /init/init.sql > /tmp/init.sql +echo "[tdengine-init] 待执行语句数:$(wc -l < /tmp/init.sql)" + +echo "[tdengine-init] 执行建库建表" +taos -h tdengine -f /tmp/init.sql + +echo "[tdengine-init] 校验超级表" +missing=0 +for pair in "iotlcm_daily:t_sim_card_usage_daily" "iotlcm_monthly:t_sim_card_usage_monthly"; do + db=${pair%%:*} + stable=${pair##*:} + if taos -h tdengine -s "show ${db}.stables;" 2>/dev/null | grep -q "$stable"; then + echo " OK ${db}.${stable}" + else + echo " MISS ${db}.${stable}" + missing=1 + fi +done + +if [ "$missing" -ne 0 ]; then + echo "[tdengine-init] 失败:超级表缺失,请检查 taos 输出" + exit 1 +fi + +echo "[tdengine-init] 完成"