diff --git a/01_Projects/Infrastructure/Services/PowerDNS Auth/Powerdns Docker Compose.md b/01_Projects/Infrastructure/Services/PowerDNS Auth/Powerdns Docker Compose.md new file mode 100644 index 0000000..bb24bee --- /dev/null +++ b/01_Projects/Infrastructure/Services/PowerDNS Auth/Powerdns Docker Compose.md @@ -0,0 +1,679 @@ +--- +tags: + - installation + - dns + - powerdns +created: 2026-06-17 +--- + +# PowerDNS Docker Compose + +## Purpose + +This note describes how to install a PowerDNS Authoritative environment with: + +- PostgreSQL 16 +- PowerDNS Authoritative 5.0.4 +- Poweradmin +- pgweb +- scheduled PostgreSQL backups + +This installation model assumes: + +1. DNS is exposed on host port `53` +2. the PowerDNS API is only exposed on `127.0.0.1:8081` +3. web management services are published through a reverse proxy such as Traefik + +## Component versions + +| Component | Version | +| --- | --- | +| PostgreSQL | `16` | +| PowerDNS Authoritative | `5.0.4` | +| Poweradmin | `stable` | +| pgweb | `0.16.2` | +| Backup container | `alpine:3.20` | + +## Before you start + +Make sure the target host has: + +1. Linux +2. Docker +3. `docker compose` +4. port `53/tcp` and `53/udp` available +5. a reverse proxy network if Poweradmin and pgweb will be published through Traefik +6. public DNS names for the web interfaces if they will be exposed externally + +## Deployment values to prepare + +Prepare all runtime values before starting the stack. + +### Required core values + +| Variable | Meaning | +| --- | --- | +| `PGUSER` | PostgreSQL administrative user | +| `PGPASSWORD` | PostgreSQL administrative password | +| `DB_NAME` | Main PowerDNS database | +| `DB_USER` | Application database user | +| `DB_PASS` | Application database password | +| `PDNS_API_KEY` | PowerDNS API key | +| `CRON_SCHEDULE` | Backup schedule | +| `PA_SESSION_KEY` | Poweradmin session secret | +| `PA_ADMIN_USERNAME` | Bootstrap Poweradmin admin username | +| `PA_ADMIN_PASSWORD` | Bootstrap Poweradmin admin password | +| `PA_ADMIN_EMAIL` | Bootstrap Poweradmin admin email | +| `PA_ADMIN_FULLNAME` | Bootstrap Poweradmin admin full name | +| `PGWEB_USER` | pgweb login username | +| `PGWEB_PASS` | pgweb login password | + +### Optional values with defaults + +| Variable | Default | Meaning | +| --- | --- | --- | +| `TZ` | `Asia/Shanghai` | Service timezone | +| `DB_HOST` | `db` | Database hostname | +| `DB_PORT` | `5432` | Database port | +| `ADMIN_DB` | `pdnsadmin` | Admin database used for bootstrap and restore | +| `RETENTION_DAYS` | `7` | Backup age retention | +| `MAX_BACKUPS` | `7` | Number of backup sets to keep | +| `DUMP_ROLES` | `true` | Include PostgreSQL role dump in backups | +| `PDNS_VERSION` | `49` | Poweradmin PowerDNS compatibility mode | +| `DNS_NS1` | `ns1.wsvc.info` | Default NS1 value in Poweradmin | +| `DNS_NS2` | `ns2.wsvc.info` | Default NS2 value in Poweradmin | +| `DNS_HOSTMASTER` | `hostmaster.wsvc.info` | Default SOA hostmaster | +| `PA_APP_TITLE` | `Poweradmin` | Poweradmin UI title | +| `PA_CREATE_ADMIN` | `1` | Enable bootstrap admin creation | + +### Example values + +Replace every placeholder with your own values: + +```env +TZ=Asia/Shanghai +PGUSER=postgres +PGPASSWORD= +DB_HOST=db +DB_PORT=5432 +DB_NAME=pdns +DB_USER=pdns +DB_PASS= +ADMIN_DB=pdnsadmin +CRON_SCHEDULE=0 3 * * * +RETENTION_DAYS=7 +MAX_BACKUPS=7 +DUMP_ROLES=true +PDNS_API_KEY= +PA_SESSION_KEY= +PA_ADMIN_USERNAME=admin +PA_ADMIN_PASSWORD= +PA_ADMIN_EMAIL=admin@example.com +PA_ADMIN_FULLNAME=DNS Administrator +PGWEB_USER=pgweb +PGWEB_PASS= +PDNS_VERSION=49 +DNS_NS1=ns1.example.com +DNS_NS2=ns2.example.com +DNS_HOSTMASTER=hostmaster.example.com +PA_APP_TITLE=Poweradmin +PA_CREATE_ADMIN=1 +``` + +## Full configuration blocks + +Use the following full configuration content as the installation baseline. + +### Environment file + +```env +TZ=Asia/Shanghai +PGUSER=postgres +PGPASSWORD= +DB_HOST=db +DB_PORT=5432 +DB_NAME=pdns +DB_USER=pdns +DB_PASS= +ADMIN_DB=pdnsadmin +CRON_SCHEDULE=0 3 * * * +RETENTION_DAYS=7 +MAX_BACKUPS=7 +DUMP_ROLES=true +PDNS_API_KEY= +PA_SESSION_KEY= +PA_ADMIN_USERNAME=admin +PA_ADMIN_PASSWORD= +PA_ADMIN_EMAIL=admin@example.com +PA_ADMIN_FULLNAME=DNS Administrator +PGWEB_USER=pgweb +PGWEB_PASS= +PDNS_VERSION=49 +DNS_NS1=ns1.example.com +DNS_NS2=ns2.example.com +DNS_HOSTMASTER=hostmaster.example.com +PA_APP_TITLE=Poweradmin +PA_CREATE_ADMIN=1 +``` + +### Docker Compose configuration + +```yaml +networks: + frontend: + name: traefik + external: true + + backend: + internal: true + + edge: + +services: + db: + image: postgres:16 + container_name: pdns-db + environment: + POSTGRES_DB: postgres + POSTGRES_USER: ${PGUSER:?missing PGUSER} + POSTGRES_PASSWORD: ${PGPASSWORD:?missing PGPASSWORD} + TZ: ${TZ:-Asia/Shanghai} + PGTZ: ${TZ:-Asia/Shanghai} + volumes: + - dbdata:/var/lib/postgresql + - ./db-init-generated:/docker-entrypoint-initdb.d:ro + - ./backup:/backup:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] + interval: 10s + timeout: 5s + retries: 10 + restart: unless-stopped + networks: [backend, edge] + + auth: + image: powerdns/pdns-auth-50:5.0.4 + container_name: pdns-auth + depends_on: + db: + condition: service_healthy + ports: + - "53:53/udp" + - "53:53/tcp" + - "127.0.0.1:8081:8081" + environment: + PDNS_API_KEY: ${PDNS_API_KEY:?missing PDNS_API_KEY} + DB_NAME: ${DB_NAME:?missing DB_NAME} + DB_USER: ${DB_USER:?missing DB_USER} + DB_PASS: ${DB_PASS:?missing DB_PASS} + TEMPLATE_FILES: secrets + volumes: + - ./auth/pdns.conf:/etc/powerdns/pdns.conf:ro + - ./auth/templates.d:/etc/powerdns/templates.d:ro + - ./auth/keys:/var/lib/powerdns + - ./auth/import:/import + - ./auth/export:/export + - ./auth/logs:/var/log/pdns + healthcheck: + test: + [ + "CMD-SHELL", + "python3 -c \"import json, os, urllib.request; req = urllib.request.Request('http://127.0.0.1:8081/api/v1/servers/localhost', headers={'X-API-Key': os.environ['PDNS_API_KEY']}); data = json.load(urllib.request.urlopen(req, timeout=3)); assert data['daemon_type'] == 'authoritative'\"" + ] + interval: 10s + timeout: 5s + retries: 12 + restart: unless-stopped + networks: [backend, edge] + + poweradmin: + image: poweradmin/poweradmin:stable + container_name: poweradmin + depends_on: + db: + condition: service_healthy + auth: + condition: service_healthy + environment: + DB_TYPE: pgsql + DB_HOST: ${DB_HOST:-db} + DB_PORT: ${DB_PORT:-5432} + DB_NAME: ${DB_NAME:?missing DB_NAME} + DB_USER: ${DB_USER:?missing DB_USER} + DB_PASS: ${DB_PASS:?missing DB_PASS} + PA_PDNS_API_URL: http://auth:8081 + PA_PDNS_API_KEY: ${PDNS_API_KEY:?missing PDNS_API_KEY} + PA_DNS_BACKEND: sql + PDNS_VERSION: ${PDNS_VERSION:-49} + DNS_NS1: ${DNS_NS1:-ns1.wsvc.info} + DNS_NS2: ${DNS_NS2:-ns2.wsvc.info} + DNS_HOSTMASTER: ${DNS_HOSTMASTER:-hostmaster.wsvc.info} + PA_APP_TITLE: ${PA_APP_TITLE:-Poweradmin} + PA_TIMEZONE: ${TZ:-Asia/Shanghai} + PA_SESSION_KEY: ${PA_SESSION_KEY:?missing PA_SESSION_KEY} + PA_CREATE_ADMIN: ${PA_CREATE_ADMIN:-1} + PA_ADMIN_USERNAME: ${PA_ADMIN_USERNAME:?missing PA_ADMIN_USERNAME} + PA_ADMIN_PASSWORD: ${PA_ADMIN_PASSWORD:?missing PA_ADMIN_PASSWORD} + PA_ADMIN_EMAIL: ${PA_ADMIN_EMAIL:?missing PA_ADMIN_EMAIL} + PA_ADMIN_FULLNAME: ${PA_ADMIN_FULLNAME:?missing PA_ADMIN_FULLNAME} + TRUSTED_PROXIES: private_ranges + DEBUG: "false" + restart: unless-stopped + networks: [backend, frontend] + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik" + - "traefik.http.routers.poweradmin.rule=Host(`pdns.wsvc.info`)" + - "traefik.http.routers.poweradmin.entrypoints=websecure" + - "traefik.http.routers.poweradmin.tls.certresolver=letsencrypt" + - "traefik.http.services.poweradmin.loadbalancer.server.port=80" + + backup: + image: alpine:3.20 + container_name: pdns-backup + depends_on: + db: + condition: service_healthy + environment: + TZ: ${TZ:-Asia/Shanghai} + DB_HOST: ${DB_HOST:-db} + DB_PORT: ${DB_PORT:-5432} + DB_USER: ${PGUSER:?missing PGUSER} + DB_PASS: ${PGPASSWORD:?missing PGPASSWORD} + DB_NAME: ${DB_NAME:?missing DB_NAME} + RETENTION_DAYS: ${RETENTION_DAYS:-7} + MAX_BACKUPS: ${MAX_BACKUPS:-7} + DUMP_ROLES: ${DUMP_ROLES:-true} + CRON_SCHEDULE: ${CRON_SCHEDULE:?missing CRON_SCHEDULE} + volumes: + - ./backup:/backup + - ./scripts:/scripts:ro + entrypoint: > + sh -c ' + apk add --no-cache postgresql16-client tzdata util-linux bash coreutils findutils; + ln -snf /usr/share/zoneinfo/$$TZ /etc/localtime && echo $$TZ > /etc/timezone; + echo "$$DB_HOST:$$DB_PORT:*:$$DB_USER:$$DB_PASS" > /root/.pgpass; + chmod 600 /root/.pgpass; + echo "$$CRON_SCHEDULE flock -n /backup/.backup.lock /scripts/backup.sh >> /backup/backup.log 2>&1" > /etc/crontabs/root; + echo "[$$(date -Iseconds)] cron started with schedule: $$CRON_SCHEDULE" >> /backup/backup.log; + crond -f -l 8 + ' + restart: unless-stopped + networks: [backend] + + pgweb: + image: sosedoff/pgweb:0.16.2 + container_name: pdns_pgweb + restart: unless-stopped + environment: + PGWEB_DATABASE_URL: "postgres://${PGUSER:?missing PGUSER}:${PGPASSWORD:?missing PGPASSWORD}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:?missing DB_NAME}?sslmode=disable" + PGWEB_AUTH_USER: ${PGWEB_USER:?missing PGWEB_USER} + PGWEB_AUTH_PASS: ${PGWEB_PASS:?missing PGWEB_PASS} + TZ: ${TZ:-Asia/Shanghai} + depends_on: + db: + condition: service_healthy + networks: [backend, frontend] + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik" + - "traefik.http.routers.pgweb.rule=Host(`pgweb.wsvc.info`)" + - "traefik.http.routers.pgweb.entrypoints=websecure" + - "traefik.http.routers.pgweb.tls.certresolver=letsencrypt" + - "traefik.http.services.pgweb.loadbalancer.server.port=8081" + +volumes: + dbdata: {} +``` + +### PowerDNS daemon configuration + +```ini +local-address=0.0.0.0 +local-port=53 + +launch=gpgsql +gpgsql-host=db +include-dir=/etc/powerdns/pdns.d +gpgsql-dnssec=yes +allow-axfr-ips=202.91.35.141 +also-notify=202.91.35.141 +primary=yes +secondary=no + +api=yes +webserver=yes +webserver-address=0.0.0.0 +webserver-port=8081 +webserver-allow-from=127.0.0.1,172.16.0.0/12,10.0.0.0/8,192.168.0.0/16 + +version-string=anonymous +disable-syslog=yes +loglevel=4 + +default-soa-edit=INCEPTION-INCREMENT +default-soa-edit-signed=INCEPTION-INCREMENT + +disable-axfr=no +``` + +### Runtime-rendered secret template + +```ini +gpgsql-dbname={{ DB_NAME }} +gpgsql-user={{ DB_USER }} +gpgsql-password={{ DB_PASS }} +api-key={{ PDNS_API_KEY }} +``` + +## Network layout + +The stack uses three networks: + +| Network | Purpose | +| --- | --- | +| `backend` | internal service-to-service traffic | +| `edge` | local host-facing DNS and API exposure | +| `frontend` | reverse-proxy-facing web traffic | + +Service attachment: + +| Service | Networks | +| --- | --- | +| PostgreSQL | `backend`, `edge` | +| PowerDNS auth | `backend`, `edge` | +| Poweradmin | `backend`, `frontend` | +| Backup | `backend` | +| pgweb | `backend`, `frontend` | + +## Installation procedure + +### 1. Prepare the host + +Install Docker and Docker Compose support on the Linux host. + +If a reverse proxy network is required, create it before deployment. + +### 2. Prepare runtime configuration + +Set all required environment values. + +At minimum, verify: + +1. PostgreSQL admin credentials are defined +2. application database credentials are defined +3. PowerDNS API key is defined +4. Poweradmin admin account values are defined +5. pgweb login values are defined +6. backup schedule and retention values are defined + +### 3. Start the stack + +Start the stack in detached mode. + +Expected startup order: + +1. PostgreSQL starts first +2. database bootstrap creates the application role and databases +3. PowerDNS starts after PostgreSQL is healthy +4. Poweradmin starts after both PostgreSQL and PowerDNS are healthy +5. backup service starts cron after PostgreSQL is healthy +6. pgweb starts after PostgreSQL is healthy + +### 4. Wait for health checks + +The deployment should be considered ready only after: + +1. PostgreSQL passes `pg_isready` +2. PowerDNS API health check returns an authoritative daemon result +3. Poweradmin and pgweb become reachable through the reverse proxy + +### 5. Complete first access + +After startup: + +1. sign in to Poweradmin with the bootstrap admin account +2. verify pgweb login works +3. verify the PowerDNS API is reachable from the local host only +4. verify DNS answers on port `53` + +## Service configuration details + +### PostgreSQL + +| Setting | Value | +| --- | --- | +| Image | `postgres:16` | +| Container name | `pdns-db` | +| Restart policy | `unless-stopped` | +| Startup database | `postgres` | +| Health check | `pg_isready -U $POSTGRES_USER -d $POSTGRES_DB` | +| Timezone | `TZ`, `PGTZ` | + +Bootstrap behavior: + +1. create the role named by `DB_USER` if missing +2. update the password of `DB_USER` from `DB_PASS` +3. create the database named by `DB_NAME` if missing +4. create the database named by `ADMIN_DB` if missing +5. assign ownership of both databases to `DB_USER` + +### PowerDNS Authoritative + +| Setting | Value | +| --- | --- | +| Image | `powerdns/pdns-auth-50:5.0.4` | +| Container name | `pdns-auth` | +| Restart policy | `unless-stopped` | +| Published ports | `53/tcp`, `53/udp`, `127.0.0.1:8081` | +| Health check | local API request using `PDNS_API_KEY` | + +Environment used by PowerDNS: + +| Variable | Meaning | +| --- | --- | +| `PDNS_API_KEY` | API authentication key | +| `DB_NAME` | PowerDNS PostgreSQL database | +| `DB_USER` | PowerDNS PostgreSQL user | +| `DB_PASS` | PowerDNS PostgreSQL password | +| `TEMPLATE_FILES=secrets` | enables runtime rendering of dynamic config | + +Rendered runtime directives: + +| Directive | Source | +| --- | --- | +| `gpgsql-dbname` | `DB_NAME` | +| `gpgsql-user` | `DB_USER` | +| `gpgsql-password` | `DB_PASS` | +| `api-key` | `PDNS_API_KEY` | + +Daemon behavior: + +| Directive | Value | Meaning | +| --- | --- | --- | +| `local-address` | `0.0.0.0` | listen on all container interfaces | +| `local-port` | `53` | DNS listener port | +| `launch` | `gpgsql` | PostgreSQL backend | +| `gpgsql-host` | `db` | database service hostname | +| `gpgsql-dnssec` | `yes` | DNSSEC enabled | +| `api` | `yes` | API enabled | +| `webserver` | `yes` | embedded web server enabled | +| `webserver-address` | `0.0.0.0` | listen on all container interfaces | +| `webserver-port` | `8081` | API port | +| `webserver-allow-from` | `127.0.0.1,172.16.0.0/12,10.0.0.0/8,192.168.0.0/16` | restrict API access to local and private ranges | +| `primary` | `yes` | primary DNS role enabled | +| `secondary` | `no` | secondary role disabled | +| `allow-axfr-ips` | `202.91.35.141` | allowed AXFR peer | +| `also-notify` | `202.91.35.141` | notify destination | +| `version-string` | `anonymous` | hide version string | +| `disable-syslog` | `yes` | disable syslog | +| `loglevel` | `4` | logging verbosity | +| `default-soa-edit` | `INCEPTION-INCREMENT` | SOA serial update policy | +| `default-soa-edit-signed` | `INCEPTION-INCREMENT` | SOA serial policy for signed zones | +| `disable-axfr` | `no` | AXFR globally allowed if otherwise permitted | + +### Poweradmin + +| Setting | Value | +| --- | --- | +| Image | `poweradmin/poweradmin:stable` | +| Container name | `poweradmin` | +| Restart policy | `unless-stopped` | +| Database type | `pgsql` | +| API endpoint | `http://auth:8081` | +| DNS backend mode | `sql` | +| Trusted proxies | `private_ranges` | +| Debug | `false` | + +Important Poweradmin values: + +| Variable | Value | +| --- | --- | +| `DB_HOST` | `db` by default | +| `DB_PORT` | `5432` by default | +| `DB_NAME` | required | +| `DB_USER` | required | +| `DB_PASS` | required | +| `PA_PDNS_API_KEY` | same value as `PDNS_API_KEY` | +| `PDNS_VERSION` | `49` | +| `DNS_NS1` | configurable default nameserver | +| `DNS_NS2` | configurable default nameserver | +| `DNS_HOSTMASTER` | configurable default hostmaster | +| `PA_APP_TITLE` | `Poweradmin` by default | +| `PA_TIMEZONE` | value from `TZ` | +| `PA_SESSION_KEY` | required | +| `PA_CREATE_ADMIN` | `1` by default | +| `PA_ADMIN_USERNAME` | required | +| `PA_ADMIN_PASSWORD` | required | +| `PA_ADMIN_EMAIL` | required | +| `PA_ADMIN_FULLNAME` | required | + +Reverse proxy routing: + +| Item | Value | +| --- | --- | +| Router host | `pdns.wsvc.info` | +| Entry point | `websecure` | +| TLS resolver | `letsencrypt` | +| Internal service port | `80` | + +### Backup service + +| Setting | Value | +| --- | --- | +| Image | `alpine:3.20` | +| Container name | `pdns-backup` | +| Restart policy | `unless-stopped` | +| Trigger mode | cron inside the container | +| Locking | `flock -n` | + +Startup behavior: + +1. install PostgreSQL client tools and required shell utilities +2. set container timezone +3. build `/root/.pgpass` for unattended database access +4. write the cron job using `CRON_SCHEDULE` +5. start cron in foreground mode + +Backup variables: + +| Variable | Value | +| --- | --- | +| `DB_HOST` | `db` by default | +| `DB_PORT` | `5432` by default | +| `DB_USER` | same as `PGUSER` | +| `DB_PASS` | same as `PGPASSWORD` | +| `DB_NAME` | required | +| `RETENTION_DAYS` | `7` by default | +| `MAX_BACKUPS` | `7` by default | +| `DUMP_ROLES` | `true` by default | +| `CRON_SCHEDULE` | required | + +Backup behavior: + +1. dump the configured application database +2. optionally dump PostgreSQL roles +3. remove stale temporary files +4. remove zero-byte role dumps +5. prune backups older than `RETENTION_DAYS` +6. keep only the newest `MAX_BACKUPS` backup sets + +Backup artifact patterns: + +| Artifact | Pattern | +| --- | --- | +| Database dump | `pdns_YYYY-MM-DD_HH-MM-SS.sql.gz` | +| Roles dump | `roles_YYYY-MM-DD_HH-MM-SS.sql` | + +### pgweb + +| Setting | Value | +| --- | --- | +| Image | `sosedoff/pgweb:0.16.2` | +| Container name | `pdns_pgweb` | +| Restart policy | `unless-stopped` | +| Database URL mode | PostgreSQL DSN with `sslmode=disable` | +| Login user | `PGWEB_USER` | +| Login password | `PGWEB_PASS` | + +Reverse proxy routing: + +| Item | Value | +| --- | --- | +| Router host | `pgweb.wsvc.info` | +| Entry point | `websecure` | +| TLS resolver | `letsencrypt` | +| Internal service port | `8081` | + +## Port exposure summary + +| Host binding | Container port | Purpose | +| --- | --- | --- | +| `53/udp` | `53/udp` | DNS over UDP | +| `53/tcp` | `53/tcp` | DNS over TCP | +| `127.0.0.1:8081` | `8081` | local PowerDNS API | + +## Post-install checks + +After installation, verify all of the following: + +1. PostgreSQL is healthy and reachable by dependent services +2. PowerDNS answers on TCP and UDP port `53` +3. the PowerDNS API responds on `127.0.0.1:8081` +4. the API is not exposed on public interfaces +5. Poweradmin login works with the configured admin account +6. pgweb login works with the configured credentials +7. scheduled backups are being created and rotated + +## Restore note + +Restore should be treated as destructive. + +Expected restore flow: + +1. terminate active connections to the target database +2. optionally restore PostgreSQL roles +3. drop the target database +4. recreate the target database with `DB_USER` as owner +5. import the selected SQL dump +6. reassign schema ownership if needed + +Restore control values: + +| Variable | Meaning | +| --- | --- | +| `DB_NAME` | target database to recreate | +| `DB_USER` | owner of the restored database | +| `ADMIN_DB` | administrative database used during restore | +| `ADMIN_USER` | administrative PostgreSQL user, default `postgres` | +| `RESTORE_ROLES` | `auto`, `always`, or `never` | +| `CONFIRM_RESTORE=YES` | skip confirmation prompt | +| `ROLES_FILE` | explicit role dump file | + +## Security notes + +1. Keep live secrets out of static service config where possible. +2. Bind the PowerDNS API only to loopback or trusted private ranges. +3. Restrict AXFR and notify peers to trusted IP addresses only. +4. Publish Poweradmin and pgweb through HTTPS only. +5. Protect backup files so only intended operators can read them. diff --git a/01_Projects/Work/Enterprise/AIoT/AIOT compare Jetlinks.md b/01_Projects/Work/Enterprise/AIoT/AIOT compare Jetlinks.md new file mode 100644 index 0000000..4f6e579 --- /dev/null +++ b/01_Projects/Work/Enterprise/AIoT/AIOT compare Jetlinks.md @@ -0,0 +1,573 @@ +# 当前 AIOT 项目组与 JetLinks Community 功能对比 + +## 1. 对比目标 + +本文重点从“物联网管理平台功能”角度,对比当前 AIOT 项目组与 JetLinks Community。 + +对比对象: + +- 当前项目组:`aiot-admin`、`aiot-access`、`aiot-core`、`aiot-dao`、`aiot-datac`、`aiot-task` +- JetLinks Community: + +本文不重点讨论底层技术架构,只关注平台能力、业务功能和产品完整度。 + +## 2. 总体结论 + +当前 AIOT 项目组已经覆盖物联网管理平台的主要功能,包括: + +- 产品管理 +- 设备管理 +- 物模型管理 +- 设备接入 +- MQTT/HTTP 接入 +- 设备数据采集 +- TDengine 时序数据 +- 告警规则 +- 设备联动 +- 编解码管理 +- GIS 管理 +- 工单管理 +- 平台级联 +- 数据共享 + +JetLinks Community 的功能覆盖面更偏“通用物联网平台底座”,在以下方面更完整: + +- 多协议统一接入 +- 协议插件化 +- 网络组件管理 +- 规则引擎平台化 +- 通知组件 +- 数据可视化 +- 权限体系 +- 日志体系 +- 设备模拟器 +- 通用平台扩展能力 + +因此: + +- 当前项目更像面向具体业务场景落地的 AIOT 管理平台。 +- JetLinks 更像可二次开发的通用物联网基础平台。 + +## 3. 功能总览对比 + +| 功能域 | 当前 AIOT 项目组 | JetLinks Community | 对比结论 | +|---|---|---|---| +| 产品管理 | 支持产品、产品功能、产品主题、产品分类等 | 支持产品、物模型、协议绑定等 | 两者均具备,JetLinks 抽象更通用 | +| 设备管理 | 支持设备、分组、类型、分类、图片、认证等 | 支持设备生命周期、状态、配置、实例管理 | 两者均具备,当前项目业务页面更直接 | +| 物模型 | 支持 schema、功能、事件、属性等能力 | 支持 Thing Model,模型体系更标准化 | JetLinks 物模型体系更平台化 | +| 设备接入 | 支持 MQTT、HTTP、OneNET、AEP 等 | 支持 MQTT、TCP、UDP、HTTP、CoAP 等 | JetLinks 协议接入覆盖更广 | +| 网络组件 | 有网络配置和 MQTT/HTTP 客户端能力 | 有独立 network-component | JetLinks 网络层抽象更完整 | +| 协议/编解码 | 有编解码管理、解析相关能力 | 有 protocol-component、脚本、协议包机制 | JetLinks 更适合多协议扩展 | +| 数据采集 | 有 `aiot-datac`、采集任务、MQTT 数据处理 | 有网关、消息流、时序组件 | 两者均具备,JetLinks 更偏平台化数据管道 | +| 时序数据 | 支持 TDengine/TSDB | 支持 TimescaleDB、TDengine、ES 等 | JetLinks 存储适配更丰富 | +| 告警管理 | 有告警规则、告警记录、告警通知 | 通过规则引擎、通知组件实现 | 当前项目告警业务更直接,JetLinks 扩展更强 | +| 设备联动 | 有设备联动、条件、动作、日志 | 通过规则引擎和场景规则实现 | JetLinks 规则编排能力更完整 | +| 工单管理 | 有工单、工单流程、推送配置 | 社区版核心不以工单为主 | 当前项目工单能力更贴近业务应用 | +| GIS 管理 | 有 GIS 应用、图层、图例、轮廓 | JetLinks 核心不突出 GIS | 当前项目 GIS 业务能力更明显 | +| 平台级联 | 有平台级联、订阅、审计等页面 | 可通过集成/网关扩展实现 | 当前项目已有明确业务页面 | +| 数据共享 | 有 MQTT/HTTP 数据共享处理 | 可通过规则、网关、消息订阅扩展 | 当前项目数据共享更业务化 | +| 数据可视化 | 有 dashboard、设备概览、图表组件 | 有 dashboard/visualization-manager | JetLinks 可视化平台化更强 | +| 用户权限 | 依赖内部 MINS/IBLS/Token 体系 | 有 authentication-manager | JetLinks 权限模块开源完整度更高 | +| 日志审计 | 当前项目中不作为突出模块 | 有 logging-component/manager | JetLinks 日志体系更完整 | +| 通知能力 | 有告警通知、通知相关核心能力 | 有 notify-component,短信、邮件等通知抽象 | JetLinks 通知组件更通用 | +| 设备模拟 | 有设备模拟相关页面 | 有 simulator 模块 | JetLinks 设备模拟能力更独立 | +| 运维监控 | 有 MQTT 监控、平台监控相关页面 | 有日志、网关、设备状态等监控能力 | 两者均具备,侧重点不同 | + +## 4. 产品与物模型管理 + +### 4.1 当前项目能力 + +当前项目围绕产品、设备类型、设备分类、设备模型、设备功能等组织平台能力。 + +相关功能页面包括: + +- `deviceTypeManage`:设备类型管理 +- `deviceCateManage`:设备分类管理 +- `deviceModelManage`:设备模型管理 +- `deviceFunManage`:设备功能管理 +- `deviceAccess`:设备接入配置 +- `coderDecoderManage`:编解码管理 + +相关后端能力包括: + +- `IotProductController` +- `IotProductFeatureController` +- `IotFeatureController` +- `IotCategoryController` +- `IotCodecController` +- `IotCodecFieldController` +- `aiot-core/schema` +- `aiot-core/codec` + +### 4.2 JetLinks 能力 + +JetLinks 以产品、设备、物模型、协议为核心组织管理能力。 + +典型能力包括: + +- 产品定义 +- 物模型定义 +- 属性、事件、功能定义 +- 产品与协议绑定 +- 产品与设备实例关联 +- 物模型数据处理 + +### 4.3 对比结论 + +| 方面 | 当前项目 | JetLinks | +|---|---|---| +| 产品管理 | 已具备业务化管理页面 | 更标准化、平台化 | +| 物模型 | 已有 schema 和功能模型 | Thing Model 体系更成熟 | +| 编解码 | 有专门编解码管理 | 协议包/脚本化能力更强 | +| 适合场景 | 固定业务场景快速落地 | 多行业、多协议平台化扩展 | + +## 5. 设备管理 + +### 5.1 当前项目能力 + +当前项目设备管理相关功能比较完整,覆盖设备基础信息、分组、认证、图片、订阅、模拟等。 + +相关页面包括: + +- `deviceManage`:设备管理 +- `deviceGroupManage`:设备分组 +- `deviceAuthManage`:设备认证 +- `deviceSub`:设备订阅 +- `deviceSubscribe`:设备订阅管理 +- `deviceImitate`:设备模拟 +- `deviceView`:设备视图 +- `deviceOverview` / `deviceOverviewNew`:设备概览 + +相关后端能力包括: + +- `IotDerviceController` +- `IotDeviceGroupController` +- `IotDerviceDataController` +- `IotDerviceEventController` +- `IotDerviceCmdController` +- `IotDervicePicController` +- `IotDeviceStatisticsController` + +### 5.2 JetLinks 能力 + +JetLinks 设备管理能力更偏平台标准化,通常包括: + +- 设备实例管理 +- 设备状态管理 +- 设备属性、事件、功能调用 +- 设备分组/标签/关系 +- 设备消息上下行 +- 设备影子/配置 +- 设备生命周期管理 + +### 5.3 对比结论 + +当前项目设备管理页面更贴合现有业务;JetLinks 设备模型更通用,适合大规模、多协议、多产品线设备统一管理。 + +## 6. 设备接入与协议管理 + +### 6.1 当前项目能力 + +当前项目设备接入主要由 `aiot-access` 和 `aiot-core` 承担。 + +已观察到的能力包括: + +- MQTT 接入 +- HTTP 接入 +- OneNET 接入 +- AEP 接入 +- 网络配置 +- 数据共享 +- MQTT 消息服务 +- 设备缓存 +- 自定义 Topic + +相关目录: + +```text +aiot-access/aiot-access-server/src/main/java/com/aifa/mins/aiot/access/core/network +aiot-access/aiot-access-server/src/main/java/com/aifa/mins/aiot/access/mqtt +aiot-access/aiot-access-server/src/main/java/com/aifa/mins/aiot/access/datashare +aiot-core/src/main/java/com/aifa/mins/aiot/core/mqtt +aiot-core/src/main/java/com/aifa/mins/aiot/core/emq +``` + +### 6.2 JetLinks 能力 + +JetLinks 在设备接入方面是核心优势之一,支持: + +- MQTT +- TCP +- UDP +- HTTP +- CoAP +- TLS/DTLS +- 网关接入 +- 协议插件 +- 网络组件管理 +- 设备消息统一转换 + +### 6.3 对比结论 + +| 方面 | 当前项目 | JetLinks | +|---|---|---| +| MQTT | 支持 | 支持 | +| HTTP | 支持 | 支持 | +| TCP/UDP | 未作为明显功能暴露 | 支持 | +| CoAP | 未作为明显功能暴露 | 支持 | +| 第三方平台接入 | OneNET、AEP 更明确 | 可通过协议/网关扩展 | +| 协议插件化 | 有编解码能力,但插件化程度有限 | 更成熟 | +| 网络组件管理 | 有网络配置 | 独立 network-component | + +如果当前平台后续需要接入更多厂家、更多私有协议,JetLinks 的协议与网络组件设计值得重点参考。 + +## 7. 数据采集与时序数据 + +### 7.1 当前项目能力 + +当前项目的数据相关能力主要包括: + +- MQTT 数据接收 +- 数据采集任务 +- 数据转换处理 +- TDengine/TAOS 数据存储 +- 设备数据查询 +- 事件数据管理 + +相关模块: + +- `aiot-datac` +- `aiot-core/tsdb` +- `aiot-admin` 中的设备数据、事件数据、TDengine 查询页面 + +相关页面包括: + +- `eventData` +- `deviceOverview` +- `deviceOverviewNew` +- `plfmMonitor` + +### 7.2 JetLinks 能力 + +JetLinks 在数据处理上提供更平台化的能力: + +- 设备消息统一处理 +- 属性、事件、功能调用数据处理 +- 时序数据组件 +- TDengine 集成 +- TimescaleDB 支持 +- Elasticsearch 可选支持 +- 数据可视化组件 + +### 7.3 对比结论 + +当前项目已经能支撑业务场景下的数据采集和 TDengine 存储;JetLinks 的优势在于数据模型更统一,时序数据存储适配更开放。 + +## 8. 告警与规则联动 + +### 8.1 当前项目能力 + +当前项目告警和联动相关功能比较明确。 + +相关页面包括: + +- `warnRuleManage`:告警规则管理 +- `deviceWarn`:设备告警 +- `deviceLinkage`:设备联动 + +相关后端能力包括: + +- `IotWarmRuleController` +- `IotWarmRecordController` +- `IotWarmNoticeController` +- `IotWarmSettingController` +- `IotDeviceLinkageController` +- `IotDeviceLinkageConditionController` +- `IotDeviceLinkageActionController` +- `IotDeviceLinkageActionLogController` +- `aiot-core/rule` +- `aiot-core/warn` + +### 8.2 JetLinks 能力 + +JetLinks 将规则引擎作为核心能力,通常可覆盖: + +- 设备数据触发 +- 条件判断 +- 场景联动 +- 告警触发 +- 通知发送 +- 数据转发 +- 脚本处理 +- 规则执行日志 + +### 8.3 对比结论 + +当前项目已经有完整的告警和设备联动业务闭环,适合直接支撑具体业务。 + +JetLinks 的规则引擎更通用,适合把告警、联动、数据转发、消息处理统一成规则编排平台。 + +建议当前项目重点借鉴 JetLinks 的: + +- 规则模型抽象 +- 触发器设计 +- 条件表达式设计 +- 动作执行器设计 +- 规则执行日志 +- 通知组件解耦 + +## 9. 工单、GIS 与行业业务能力 + +### 9.1 当前项目能力 + +当前项目明显包含一些行业业务功能: + +- 工单管理 +- GIS 应用管理 +- GIS 图层管理 +- GIS 图例管理 +- GIS 轮廓管理 +- 平台级联 +- 审核订阅 + +相关页面包括: + +- `workOrder` +- `GISApplication` +- `GisAppManage` +- `GisLayerManage` +- `GisLegendManage` +- `GisOutlineManage` +- `platformCascade` +- `subAuditManage` + +相关后端能力包括: + +- `IotWorkOrderController` +- `IotWorkOrderProcessController` +- `GisAppController` +- `GisLayerController` +- `GisLegendController` +- `GisOutlineController` +- `IotPlatformCascadeController` +- `IotPlatformRssController` + +### 9.2 JetLinks 能力 + +JetLinks Community 更偏通用物联网平台底座,核心关注: + +- 设备接入 +- 设备管理 +- 规则引擎 +- 数据处理 +- 通知 +- 可视化 +- 权限 +- 日志 + +工单、GIS、行业流程并不是 JetLinks 社区版最核心的功能重点,通常需要在业务层二次开发。 + +### 9.3 对比结论 + +在行业业务管理方面,当前项目比 JetLinks Community 更贴近具体业务落地。 + +如果当前项目已经服务于某个行业场景,如园区、城市、能源、设备运维等,保留当前业务层更合理。 + +## 10. 运维监控与平台管理 + +### 10.1 当前项目能力 + +当前项目已有一些平台运维和监控功能: + +- MQTT 监控 +- 平台监控 +- 设备统计 +- 设备概览 +- 数据采集任务 +- 任务调度 +- 系统管理页面 + +相关页面包括: + +- `plfmMonitor` +- `deviceOverview` +- `deviceOverviewNew` +- `dashboard` +- `system` + +相关后端能力包括: + +- `IotMonitorController` +- `IotMqttMonitorApi` +- `IotTdMqMonitorController` +- `IotDeviceStatisticsController` + +### 10.2 JetLinks 能力 + +JetLinks 平台管理能力包括: + +- 网关状态 +- 设备状态 +- 日志管理 +- 通知配置 +- 规则执行监控 +- 数据看板 +- 用户权限 +- 系统配置 + +### 10.3 对比结论 + +当前项目偏业务运维视角,JetLinks 偏平台运维视角。 + +如果要增强当前项目,可以考虑补充: + +- 接入网关运行状态 +- 协议实例状态 +- 规则执行链路 +- 设备消息链路追踪 +- 设备上下线日志 +- 指令下发日志 +- 数据转发日志 + +## 11. 功能成熟度评估 + +| 功能 | 当前项目成熟度 | JetLinks 成熟度 | 说明 | +|---|---:|---:|---| +| 设备管理 | 高 | 高 | 两者均完整 | +| 产品/物模型 | 中高 | 高 | JetLinks 标准化更强 | +| MQTT 接入 | 高 | 高 | 两者均支持 | +| HTTP 接入 | 中高 | 高 | JetLinks 接入抽象更统一 | +| TCP/UDP/CoAP | 低/未明显体现 | 高 | JetLinks 覆盖更广 | +| 编解码 | 中高 | 高 | JetLinks 协议包机制更强 | +| 告警规则 | 高 | 高 | 当前业务闭环更直接 | +| 规则引擎 | 中 | 高 | JetLinks 更平台化 | +| TDengine | 高 | 中高 | 当前项目绑定更明显 | +| 多时序存储 | 中 | 高 | JetLinks 适配更多 | +| GIS | 高 | 低/需二开 | 当前项目优势 | +| 工单 | 高 | 低/需二开 | 当前项目优势 | +| 平台级联 | 中高 | 中/需扩展 | 当前项目已有业务页面 | +| 数据可视化 | 中 | 中高 | JetLinks visualization-manager 更平台化 | +| 权限管理 | 中/依赖内部体系 | 高 | JetLinks 开源体系更完整 | +| 日志审计 | 中 | 高 | JetLinks 日志模块更突出 | + +## 12. 当前项目优势 + +当前 AIOT 项目组的优势主要在业务落地能力: + +1. 已有完整管理端页面。 +2. 已有具体行业功能,如 GIS、工单、平台级联。 +3. 已有设备接入、设备管理、告警、联动闭环。 +4. 已接入 TDengine,适合已有数据存储方案。 +5. 已有 OneNET、AEP 等第三方平台接入能力。 +6. 与内部 MINS、权限、组织、门户等体系集成较深。 + +## 13. JetLinks 优势 + +JetLinks Community 的优势主要在平台底座能力: + +1. 多协议接入能力更全面。 +2. 网络组件、协议组件、网关组件抽象更清晰。 +3. 物模型和设备消息体系更标准化。 +4. 规则引擎更通用,可承载告警、联动、转发等场景。 +5. 通知、日志、可视化、权限等通用平台能力更完整。 +6. 开源生态更适合参考、二开和长期演进。 + +## 14. 建议借鉴方向 + +如果目标是提升当前物联网管理平台功能,建议优先从以下方向借鉴 JetLinks: + +### 14.1 强化设备接入中心 + +建议将接入能力统一抽象为: + +- 网络组件 +- 协议组件 +- 接入网关 +- 设备消息转换 +- 连接状态管理 +- 上下行日志 + +### 14.2 强化协议和编解码管理 + +建议补充或优化: + +- 协议包管理 +- 脚本化解析 +- 设备型号与协议绑定 +- Topic 与物模型映射 +- 上下行消息标准格式 + +### 14.3 强化规则引擎 + +建议将现有告警和联动统一抽象: + +- 触发源:设备属性、事件、状态、定时任务 +- 条件:表达式、阈值、时间窗口、组合条件 +- 动作:通知、指令下发、数据转发、工单创建、HTTP/MQTT 推送 +- 日志:执行记录、失败原因、重试记录 + +### 14.4 强化设备数据中心 + +建议形成统一设备数据视图: + +- 最新属性 +- 历史属性 +- 事件记录 +- 指令记录 +- 上下线记录 +- 告警记录 +- 原始报文 +- 解析后报文 + +### 14.5 强化平台运维能力 + +建议补充: + +- 接入服务状态 +- MQTT Broker 状态 +- 协议实例状态 +- 设备连接数 +- 消息吞吐量 +- 规则执行统计 +- 数据入库失败统计 +- 设备离线原因分析 + +## 15. 迁移或融合建议 + +不建议直接用 JetLinks 替换当前项目。 + +更稳妥的方式是: + +1. 保留当前业务管理端、GIS、工单、平台级联等业务功能。 +2. 重点参考 JetLinks 改造接入层、协议层、规则引擎和数据中心。 +3. 如果需要引入 JetLinks,可将 JetLinks 作为设备接入和规则引擎底座,通过 API 与当前管理端集成。 +4. 当前项目继续承载行业业务和内部系统集成。 + +推荐融合形态: + +```text +当前 AIOT 管理端 + ├── 设备管理/业务页面/GIS/工单/平台级联 + ├── 对接内部权限、组织、门户 + └── 调用统一物联网底座 API + | + v +物联网底座能力 + ├── 设备接入 + ├── 协议解析 + ├── 物模型 + ├── 规则引擎 + ├── 时序数据 + └── 消息转发 +``` + +## 16. 后续建议文档 + +建议继续补充以下专题文档: + +1. 当前项目设备管理功能清单 +2. 当前项目物模型与 JetLinks Thing Model 对比 +3. 当前项目设备接入流程与 JetLinks Gateway 对比 +4. 当前项目告警/联动与 JetLinks 规则引擎对比 +5. 当前项目 TDengine 数据模型说明 +6. 当前项目 GIS、工单、平台级联业务能力说明 diff --git a/01_Projects/Work/Enterprise/AIoT/AIOT_to_JetLinks_Migration_Guide.md b/01_Projects/Work/Enterprise/AIoT/AIOT_to_JetLinks_Migration_Guide.md new file mode 100644 index 0000000..97f190d --- /dev/null +++ b/01_Projects/Work/Enterprise/AIoT/AIOT_to_JetLinks_Migration_Guide.md @@ -0,0 +1,1451 @@ +# AIOT 现有数据导入 JetLinks 实操迁移指南 + +## 1. 文档目标 + +本文说明如何把当前 AIOT 平台中已有的产品、设备、物模型、接入配置、设备数据等迁移到 JetLinks Community。 + +重点不是“整库搬迁”,而是给出一套可以实际执行的迁移工作方法: + +- 明确迁移范围 +- 明确迁移工作项 +- 明确数据映射关系 +- 明确推荐操作顺序 +- 提供可落地的导出、转换、导入步骤 +- 提供试运行、验证、上线、回滚指引 + +> 结论:不要把 AIOT 数据库直接整库导入 JetLinks。应按“产品分类 → 产品 → 物模型 → 设备 → 接入配置 → 设备数据 → 规则”的顺序做模型转换和分批导入。 + +--- + +## 2. 迁移目标与边界 + +### 2.1 推荐迁移到 JetLinks 的数据 + +第一阶段建议迁移 JetLinks 作为物联网底座所必需的数据: + +| 优先级 | 数据 | 是否必须 | 说明 | +|---|---|---|---| +| P0 | 产品分类 | 建议 | 用于产品归类 | +| P0 | 产品 | 必须 | JetLinks 设备必须归属产品 | +| P0 | 物模型 | 必须 | 设备属性、事件、功能解析依赖物模型 | +| P0 | 设备 | 必须 | 设备实例数据 | +| P0 | 设备认证配置 | 必须 | MQTT/HTTP 接入需要认证信息 | +| P0 | 接入配置 | 必须 | 协议、接入网关、Topic、解析配置 | +| P1 | 设备区域/经纬度 | 建议 | 用于地图、区域查询、业务展示 | +| P1 | 设备分组/标签 | 建议 | 用于管理和筛选 | +| P2 | 最近历史数据 | 可选 | 用于迁移后历史曲线连续 | +| P2 | 告警记录 | 可选 | 用于历史追溯 | +| P3 | 告警规则/联动规则 | 建议重建 | 模型差异大,不建议直接搬表 | + +### 2.2 不建议直接迁移到 JetLinks 的数据 + +以下数据建议继续保留在 AIOT,或作为后续二开内容: + +| AIOT 数据 | 处理建议 | 原因 | +|---|---|---| +| GIS 应用、图层、图例、轮廓 | 保留 AIOT 或二开 | JetLinks Community 核心不提供完全等价 GIS 模块 | +| 工单、工单流程 | 保留 AIOT | JetLinks 不是工单系统 | +| 平台级联、订阅审核 | 保留或按规则/消息转发重做 | 业务模型差异较大 | +| 行业定制页面配置 | 保留 AIOT | 属于业务层能力 | +| 复杂告警/联动规则 | 转换为 JetLinks 规则草稿后人工确认 | 不能直接搬表 | + +### 2.3 推荐最终形态 + +更稳妥的目标不是“JetLinks 完全替换 AIOT”,而是: + +```text +JetLinks 负责: + 产品、设备、物模型、协议接入、设备数据、规则引擎、时序数据 + +AIOT 保留: + GIS、工单、平台级联、行业页面、内部权限/组织/门户集成、历史业务流程 +``` + +也就是: + +```text +JetLinks = 物联网底座 +AIOT = 行业业务管理层 +``` + +--- + +## 3. 迁移工作分解 + +### 3.1 工作包一:数据盘点 + +目标:确认 AIOT 中有哪些数据需要迁移。 + +需要盘点: + +| 数据类型 | AIOT 可能来源 | 结果产物 | +|---|---|---| +| 产品分类 | `IotCategory` | 分类清单 CSV/JSON | +| 产品 | `IotProduct` | 产品清单 CSV/JSON | +| 产品功能/物模型 | `IotFeature`、`IotProductFeature`、`IotProduct.omDefine` | 物模型中间 JSON | +| 设备 | `IotDervice` | 设备清单 CSV/JSON | +| 分组 | `IotDeviceGroup`、`IotGroup` | 分组/标签映射 | +| 编解码 | `IotCodec`、`IotCodecField` | 协议转换清单 | +| 告警规则 | `IotWarmRule` | 规则迁移清单 | +| 设备联动 | `IotDeviceLinkage*` | 联动迁移清单 | +| 时序数据 | TDengine、`IotDerviceData`、`IotDerviceEvent` | 历史数据范围 | + +产出文件建议: + +```text +migration-workspace/ +├── 00_inventory/ +│ ├── category_count.md +│ ├── product_count.md +│ ├── device_count.md +│ ├── feature_count.md +│ └── rule_count.md +``` + +### 3.2 工作包二:字段映射 + +目标:把 AIOT 字段转换成 JetLinks 可识别的数据结构。 + +产出文件建议: + +```text +migration-workspace/ +├── 01_mapping/ +│ ├── category_mapping.csv +│ ├── product_mapping.csv +│ ├── device_mapping.csv +│ ├── feature_mapping.csv +│ ├── protocol_mapping.csv +│ └── id_mapping.csv +``` + +其中 `id_mapping.csv` 很重要,用于记录新旧 ID 对应关系: + +```csv +object_type,aiot_id,aiot_sn,jetlinks_id,status,error +product,10001,PROD_TEMP,10001,success, +device,20001,DVC001,DVC001,success, +``` + +### 3.3 工作包三:物模型转换 + +目标:将 AIOT 的功能点、属性、事件、产品定义转换成 JetLinks metadata。 + +产出文件建议: + +```text +migration-workspace/ +├── 02_transform/ +│ ├── product_10001_aiot_model.json +│ ├── product_10001_jetlinks_metadata.json +│ └── metadata_validate_report.md +``` + +### 3.4 工作包四:导入 JetLinks + +推荐调用 JetLinks API 导入主数据。 + +导入顺序: + +```text +1. 产品分类 +2. 产品,不含复杂物模型也可以先导入 +3. 更新产品物模型 metadata +4. 设备 +5. 设备认证配置 +6. 设备分组/标签 +``` + +### 3.5 工作包五:验证与切换 + +验证内容: + +```text +1. 产品数量 +2. 设备数量 +3. 物模型解析 +4. MQTT/HTTP 接入 +5. 属性上报 +6. 事件上报 +7. 指令下发 +8. 告警触发 +9. 历史数据查询 +``` + +--- + +## 4. AIOT 与 JetLinks 核心数据映射 + +### 4.1 总体映射 + +| AIOT 数据/实体 | JetLinks 目标 | 迁移方式 | 说明 | +|---|---|---|---| +| `IotCategory` | `dev_product_category` | API/DB | 产品分类 | +| `IotProduct` | `dev_product` | API 推荐 | 产品 | +| `IotFeature` | `dev_product.metadata.properties/functions/events` | 转换 | 全局功能点 | +| `IotProductFeature` | `dev_product.metadata` | 转换 | 产品功能点 | +| `IotDervice` | `dev_device_instance` | API 推荐 | 设备实例 | +| `IotDeviceGroup` | 关系/标签/扩展配置 | API/业务处理 | 分组 | +| `IotCodec` | 协议插件/脚本/配置 | 人工转换 | 不建议直接导表 | +| `IotWarmRule` | 规则引擎规则 | 半自动转换 | 建议生成为草稿 | +| `IotDeviceLinkage*` | 规则引擎/场景联动 | 半自动转换 | 建议人工审核 | +| TDengine 设备属性 | JetLinks 时序存储 | 批量迁移 | 可选 | +| TDengine 设备事件 | JetLinks 时序存储 | 批量迁移 | 可选 | +| GIS 表 | AIOT 保留 | 不迁移 | JetLinks 无直接等价模块 | +| 工单表 | AIOT 保留 | 不迁移 | JetLinks 无直接等价模块 | + +--- + +## 5. 产品分类迁移方法 + +### 5.1 AIOT 来源字段 + +AIOT 分类实体通常为: + +```text +IotCategory +``` + +字段: + +```text +sid, parentId, name, sn, lvsn, iconFont, iconPic, ordered, isLeaf, descs, status, delFlag +``` + +### 5.2 JetLinks 目标字段 + +JetLinks 产品分类: + +```text +dev_product_category +``` + +字段: + +```text +id, parent_id, key, name, description, metadata, create_time +``` + +### 5.3 字段映射 + +| AIOT | JetLinks | 处理方式 | +|---|---|---| +| `sid` | `id` | 转字符串 | +| `parentId` | `parent_id` | 根节点置空或按 JetLinks 要求处理 | +| `sn` | `key` | 如果为空,用 `cat_${sid}` | +| `name` | `name` | 原样迁移 | +| `descs` | `description` | 原样迁移 | +| `created` | `create_time` | 转毫秒时间戳 | + +### 5.4 导出模板 + +建议先导出为 CSV: + +```csv +id,parent_id,key,name,description +100,0,weather,气象设备,气象类设备 +101,100,temp_sensor,温度传感器,温度采集设备 +``` + +--- + +## 6. 产品迁移方法 + +### 6.1 AIOT 来源字段 + +AIOT 产品实体: + +```text +IotProduct +``` + +关键字段: + +```text +sid, name, sn, types, accessWay, accessFrom, accessNetwork, +categoryId, netProto, dataProtoId, dataProto, omDefine, +topicDefine, authWay, dataGatherWay, dataGatherSetting +``` + +### 6.2 JetLinks 目标字段 + +JetLinks 产品表: + +```text +dev_product +``` + +核心字段: + +```text +id, name, classified_id, classified_name, message_protocol, +metadata, transport_protocol, network_way, device_type, +access_id, access_provider, access_name, configuration, +state, store_policy, store_policy_configuration +``` + +### 6.3 字段映射 + +| AIOT | JetLinks | 处理方式 | +|---|---|---| +| `sid` | `id` | 建议保留原产品 ID,转字符串 | +| `name` | `name` | 原样迁移 | +| `categoryId` | `classified_id` | 关联分类映射 | +| 分类名称 | `classified_name` | 根据分类表补齐 | +| `dataProto` / `dataProtoId` | `message_protocol` | 映射为 JetLinks 协议 ID | +| `netProto` | `transport_protocol` | 如 `MQTT`、`HTTP` | +| `accessWay` | `network_way` / `access_provider` | 映射为 JetLinks 接入方式 | +| `topicDefine` | `configuration.topicDefine` | 放入配置保留 | +| `authWay` | `configuration.authWay` | 放入配置保留 | +| `omDefine` | `metadata` | 转换为 JetLinks 物模型 | +| `status` | `state` | 启用为 `1`,停用为 `0` | + +### 6.4 产品转换后的 JSON 示例 + +```json +{ + "id": "10001", + "name": "温湿度采集产品", + "classifiedId": "101", + "classifiedName": "环境监测设备", + "messageProtocol": "official-json", + "transportProtocol": "MQTT", + "networkWay": "mqtt", + "deviceType": "device", + "state": 1, + "configuration": { + "source": "aiot", + "originSn": "PROD_TH", + "topicDefine": [ + { + "key": "data", + "topic": "data/{deviceId}/{productId}", + "type": "publish" + } + ] + }, + "metadata": "{...JetLinks metadata json string...}" +} +``` + +> 注意:JetLinks 的 `metadata` 通常是字符串形式的 JSON,导入前要确认目标接口需要对象还是字符串。 + +--- + +## 7. 物模型转换方法 + +## 7.1 为什么物模型不能直接搬 + +AIOT 中物模型可能分散在: + +```text +IotFeature +IotProductFeature +IotProduct.omDefine +IotProduct.omDemo +IotCodec +IotCodecField +``` + +JetLinks 中物模型集中在产品的: + +```text +dev_product.metadata +``` + +物模型必须转换成 JetLinks 支持的格式,否则设备上报后无法正确解析属性、事件、功能。 + +### 7.2 建议中间模型 + +先不要直接生成 JetLinks metadata,建议先生成中间模型: + +```json +{ + "productId": "10001", + "properties": [ + { + "id": "temperature", + "name": "温度", + "type": "double", + "unit": "℃", + "read": true, + "write": false + } + ], + "events": [ + { + "id": "fault", + "name": "故障事件", + "level": "warning" + } + ], + "functions": [ + { + "id": "setThreshold", + "name": "设置阈值", + "inputs": [], + "output": null + } + ] +} +``` + +### 7.3 转 JetLinks 属性示例 + +AIOT 属性: + +```text +featureSn = temperature +name = 温度 +unit = ℃ +type = double +``` + +JetLinks 属性: + +```json +{ + "id": "temperature", + "name": "温度", + "valueType": { + "type": "double", + "unit": "℃" + }, + "expands": { + "source": "aiot" + } +} +``` + +### 7.4 类型转换表 + +| AIOT 类型 | JetLinks valueType.type | 说明 | +|---|---|---| +| int/integer | int | 整数 | +| long | long | 长整数 | +| float | float | 浮点 | +| double/number | double | 双精度 | +| string/varchar | string | 字符串 | +| bool/boolean | boolean | 布尔 | +| enum | enum | 需要补齐枚举项 | +| date/time/datetime | date | 时间 | +| object/json | object | 对象 | +| array | array | 数组 | + +### 7.5 物模型校验清单 + +每个产品导入前必须检查: + +```text +1. 属性 ID 是否唯一 +2. 属性 ID 是否只包含字母、数字、下划线、中划线 +3. 属性类型是否明确 +4. 枚举类型是否包含枚举项 +5. 事件 ID 是否唯一 +6. 功能 ID 是否唯一 +7. 功能输入参数是否完整 +8. 单位是否规范 +9. 原始 omDefine 是否已备份 +10. JetLinks 是否能成功解析 metadata +``` + +--- + +## 8. 设备迁移方法 + +### 8.1 AIOT 来源字段 + +AIOT 设备实体: + +```text +IotDervice +``` + +关键字段: + +```text +sid, name, sn, types, accessWay, accessFrom, accessNetwork, +categoryId, productId, iconPic, areaSn, areaName, +gisLon, gisLat, addrs, regAddr, busiPlaceId, busiPlaceName, +regTime, activeTime, lastOnlineTime +``` + +### 8.2 JetLinks 目标字段 + +JetLinks 设备表: + +```text +dev_device_instance +``` + +核心字段: + +```text +id, name, device_type, describe, product_id, product_name, +configuration, derive_metadata, state, registry_time, +parent_id, photo_url, create_time, modify_time +``` + +### 8.3 字段映射 + +| AIOT | JetLinks | 处理方式 | +|---|---|---| +| `sn` | `id` | 推荐使用设备编码作为 JetLinks deviceId | +| `sid` | `configuration.aiot.originDeviceId` | 保留原始 ID | +| `name` | `name` | 原样迁移 | +| `productId` | `product_id` | 使用产品 ID 映射 | +| 产品名称 | `product_name` | 关联产品补齐 | +| `types` | `device_type` | 映射为 `device`/`gateway`/`childrenDevice` | +| `iconPic` | `photo_url` | 图片地址 | +| `activeTime` / `regTime` | `registry_time` | 转毫秒时间戳 | +| 在线状态 | `state` | `online`/`offline`/`notActive` | +| `gisLon` / `gisLat` | `configuration.location` | 放入扩展配置 | +| `areaSn` / `areaName` | `configuration.area` | 放入扩展配置 | +| `addrs` | `configuration.address` | 放入扩展配置 | +| `accessWay` | `configuration.aiot.accessWay` | 保留原始接入方式 | + +### 8.4 设备 JSON 示例 + +```json +{ + "id": "DVC001", + "name": "1号温湿度传感器", + "deviceType": "device", + "productId": "10001", + "productName": "温湿度采集产品", + "state": "notActive", + "registryTime": 1719200000000, + "configuration": { + "aiot": { + "originDeviceId": "20001", + "originSn": "DVC001", + "accessWay": "mqtt" + }, + "mqtt": { + "clientId": "DVC001", + "username": "DVC001" + }, + "location": { + "longitude": 113.123, + "latitude": 23.456 + }, + "area": { + "code": "440100", + "name": "广州" + } + } +} +``` + +### 8.5 设备 ID 选择规则 + +推荐: + +```text +AIOT IotDervice.sn → JetLinks deviceId +``` + +原因: + +1. 设备编码通常已经在设备端配置。 +2. MQTT topic 中可能已经使用设备编码。 +3. 减少设备端修改。 +4. 更便于历史数据映射。 + +如果 `sn` 不合法,按以下规则清洗: + +```text +1. 去掉前后空格 +2. 中文转拼音或使用原 sid +3. 空格替换为 _ +4. 非法字符替换为 _ +5. 如果重复,加后缀 _001 +``` + +--- + +## 9. 设备认证和接入配置迁移 + +### 9.1 必须确认的信息 + +设备能否接入 JetLinks,取决于: + +```text +1. 设备 ID +2. 产品 ID +3. 协议 ID +4. 传输协议 +5. 接入网关 +6. MQTT clientId +7. MQTT username/password +8. Topic 规则 +9. 报文格式 +10. 物模型映射 +``` + +### 9.2 MQTT 迁移建议 + +如果 AIOT 设备当前使用 MQTT,可采用两种方式。 + +#### 方案 A:尽量保持设备端 Topic 不变 + +适合设备数量多、不方便改固件的情况。 + +做法: + +```text +1. 在 JetLinks 中实现或配置兼容原 AIOT Topic 的协议解析。 +2. 保持设备端 broker/topic/clientId 尽量不变。 +3. 仅切换 broker 地址或认证信息。 +``` + +#### 方案 B:按 JetLinks 官方 Topic 改造设备端 + +适合设备可控、可以 OTA 或批量修改配置的情况。 + +做法: + +```text +1. 根据 JetLinks 产品协议确定 Topic。 +2. 修改设备端上报 Topic。 +3. 修改设备端报文格式。 +4. 验证属性、事件、指令。 +``` + +### 9.3 接入配置迁移结果 + +建议把 AIOT 原始接入配置保留到 JetLinks `configuration` 中,便于排查: + +```json +{ + "aiot": { + "originAccessWay": "mqtt", + "originNetworkId": "10", + "originTopicDefine": [ + { + "key": "data", + "topic": "data/{设备编码}/{产品编码}" + } + ] + } +} +``` + +--- + +## 10. 历史数据迁移方法 + +### 10.1 是否一定要迁移历史数据 + +不一定。 + +如果目标只是让 JetLinks 接管后续设备接入,历史数据可以继续保留在 AIOT/TDengine 中。 + +推荐策略: + +```text +第一阶段:不迁移历史数据,只迁移主数据和接入能力 +第二阶段:迁移最近 1~3 个月关键属性数据 +第三阶段:按需迁移事件、告警、指令记录 +``` + +### 10.2 AIOT TDengine 事件表示例 + +当前脚本中可见事件表: + +```text +iot_dervice_event +``` + +字段: + +```text +TS, TENANT_ID, ORG_ID, DERVICE_ID, DERVICE_SN, NAME, +AREA_SN, EVENT_ID, EVENT_TITLE, EVENT_NAME, EVENT_TYPE, +EVENT_LEVEL, EVENT_BODY +``` + +### 10.3 事件数据映射 + +| AIOT | JetLinks | 说明 | +|---|---|---| +| `DERVICE_SN` | `deviceId` | 设备 ID | +| `EVENT_NAME` | `eventId` | 事件标识 | +| `EVENT_TITLE` | `eventName` | 事件名称 | +| `TS` | `timestamp` | 时间戳 | +| `EVENT_BODY` | `data` | 事件数据 | +| `EVENT_LEVEL` | `level` | 事件级别 | +| `AREA_SN` | `areaCode` | 扩展字段 | + +### 10.4 历史数据迁移原则 + +```text +1. 先迁移少量样本数据验证格式 +2. 按产品分批 +3. 按时间窗口分批 +4. 每批迁移后做数量校验 +5. 失败数据单独记录 +6. 不要在生产高峰期迁移大量历史数据 +``` + +### 10.5 历史数据批次建议 + +```text +batch_001: 最近 7 天,1 个产品,10 台设备 +batch_002: 最近 30 天,1 个产品,全部设备 +batch_003: 最近 90 天,核心产品 +batch_004: 更早数据保留原库归档 +``` + +--- + +## 11. 告警规则和联动迁移方法 + +### 11.1 不要直接导表 + +AIOT 的: + +```text +IotWarmRule +IotDeviceLinkage +IotDeviceLinkageCondition +IotDeviceLinkageAction +``` + +不建议直接写入 JetLinks 规则表。 + +原因: + +```text +1. 规则模型不同 +2. 表达式语法不同 +3. 动作执行器不同 +4. 通知组件不同 +5. 直接导入后很可能无法执行 +``` + +### 11.2 推荐迁移流程 + +```text +AIOT 规则 + ↓ +导出规则清单 + ↓ +转换为中间规则模型 + ↓ +映射 JetLinks 物模型属性/事件 + ↓ +生成 JetLinks 规则草稿 + ↓ +人工确认 + ↓ +测试环境启用 + ↓ +生产环境启用 +``` + +### 11.3 中间规则模型示例 + +```json +{ + "name": "高温告警", + "source": { + "type": "device-property", + "productId": "10001", + "property": "temperature" + }, + "condition": { + "operator": ">", + "value": 80 + }, + "actions": [ + { + "type": "notify", + "template": "温度超过阈值" + }, + { + "type": "http", + "url": "https://example.com/alarm" + } + ] +} +``` + +--- + +## 12. 实际操作指引 + +本节给出一套可以落地执行的步骤。实际接口路径和认证方式需要以你部署的 JetLinks Swagger/OpenAPI 为准。 + +### 12.1 准备目录 + +在当前项目父目录下创建迁移工作区: + +```bash +mkdir -p migration-workspace/{00_inventory,01_export,02_transform,03_import,04_report,05_backup} +``` + +建议目录结构: + +```text +migration-workspace/ +├── 00_inventory # 盘点结果 +├── 01_export # AIOT 导出原始数据 +├── 02_transform # 转换后的 JetLinks JSON +├── 03_import # 导入脚本和请求体 +├── 04_report # 迁移报告 +└── 05_backup # 备份和回滚材料 +``` + +### 12.2 备份数据 + +正式迁移前必须备份: + +```bash +# 示例:备份 AIOT MySQL,按实际库名、账号修改 +mysqldump -h -P -u -p \ + --single-transaction --default-character-set=utf8mb4 \ + > migration-workspace/05_backup/aiot_$(date +%Y%m%d_%H%M%S).sql + +# 示例:备份 JetLinks PostgreSQL,按实际配置修改 +pg_dump -h -p -U \ + > migration-workspace/05_backup/jetlinks_$(date +%Y%m%d_%H%M%S).sql +``` + +TDengine 备份请按当前 TDengine 版本使用官方导出工具或 SQL 导出。 + +### 12.3 导出 AIOT 产品分类 + +如果可以直接访问 AIOT MySQL,建议导出 CSV。 + +示例 SQL 需要按实际表名调整: + +```sql +SELECT + SID AS id, + PARENT_ID AS parent_id, + SN AS `key`, + NAME AS name, + DESCS AS description, + CREATED AS created +FROM IOT_CATEGORY +WHERE DEL_FLAG = 0; +``` + +导出文件: + +```text +migration-workspace/01_export/categories.csv +``` + +### 12.4 导出 AIOT 产品 + +示例 SQL: + +```sql +SELECT + SID, + NAME, + SN, + TYPES, + ACCESS_WAY, + ACCESS_FROM, + ACCESS_NETWORK, + CATEGORY_ID, + NET_PROTO, + DATA_PROTO_ID, + DATA_PROTO, + OM_DEFINE, + TOPIC_DEFINE, + AUTH_WAY, + DATA_GATHER_WAY, + DATA_GATHER_SETTING, + STATUS, + CREATED, + LAST_UPDATED +FROM IOT_PRODUCT +WHERE DEL_FLAG = 0; +``` + +导出文件: + +```text +migration-workspace/01_export/products.csv +``` + +### 12.5 导出 AIOT 产品功能点 + +示例 SQL: + +```sql +SELECT + pf.PRODUCT_ID, + f.SID AS FEATURE_ID, + f.SN AS FEATURE_SN, + f.NAME AS FEATURE_NAME, + f.UNIT AS UNIT, + pf.PROPS_NAME AS PROPS_NAME, + pf.DESCS AS DESCRIPTION +FROM IOT_PRODUCT_FEATURE pf +LEFT JOIN IOT_FEATURE f ON pf.FEATURE_ID = f.SID +WHERE pf.DEL_FLAG = 0; +``` + +导出文件: + +```text +migration-workspace/01_export/product_features.csv +``` + +### 12.6 导出 AIOT 设备 + +示例 SQL: + +```sql +SELECT + SID, + NAME, + SN, + TYPES, + ACCESS_WAY, + ACCESS_FROM, + ACCESS_NETWORK, + CATEGORY_ID, + PRODUCT_ID, + ICON_PIC, + AREA_SN, + AREA_NAME, + GIS_LON, + GIS_LAT, + ADDRS, + REG_ADDR, + BUSI_PLACE_ID, + BUSI_PLACE_NAME, + REG_TIME, + ACTIVE_TIME, + LAST_ONLINE_TIME, + STATUS, + CREATED, + LAST_UPDATED +FROM IOT_DERVICE +WHERE DEL_FLAG = 0; +``` + +导出文件: + +```text +migration-workspace/01_export/devices.csv +``` + +> 注意:当前代码中类名是 `IotDervice`,数据库表名需要以实际库为准,可能是 `IOT_DERVICE`。 + +### 12.7 生成 JetLinks metadata + +建议写转换脚本,将: + +```text +products.csv +product_features.csv +``` + +转换为: + +```text +migration-workspace/02_transform/products.jetlinks.jsonl +``` + +JSONL 每行一个产品,例如: + +```json +{"id":"10001","name":"温湿度产品","messageProtocol":"official-json","transportProtocol":"MQTT","metadata":"{...}"} +``` + +### 12.8 生成 JetLinks 设备 JSON + +将: + +```text +devices.csv +products.csv +``` + +转换为: + +```text +migration-workspace/02_transform/devices.jetlinks.jsonl +``` + +JSONL 每行一个设备。 + +### 12.9 导入 JetLinks 推荐方式 + +推荐使用 JetLinks API,而不是直接写库。 + +通常涉及接口: + +```text +/device-product +/device/product +/device-instance +/device/instance +``` + +实际方法、路径、认证头以 JetLinks Swagger 为准。 + +示例 curl 模板: + +```bash +JETLINKS_BASE="http://127.0.0.1:8848" +TOKEN="" + +curl -X POST "$JETLINKS_BASE/device/product" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d @migration-workspace/03_import/product_10001.json +``` + +导入设备示例: + +```bash +curl -X POST "$JETLINKS_BASE/device/instance" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d @migration-workspace/03_import/device_DVC001.json +``` + +如果当前 JetLinks 使用的不是 Bearer Token,请以实际登录接口返回的认证头为准。 + +### 12.10 批量导入脚本示例 + +可用如下伪代码实现批量导入: + +```python +import json +import requests + +BASE = "http://127.0.0.1:8848" +TOKEN = "" +HEADERS = { + "Content-Type": "application/json", + "Authorization": f"Bearer {TOKEN}", +} + +def post_json(path, data): + resp = requests.post(BASE + path, headers=HEADERS, json=data, timeout=30) + if resp.status_code >= 300: + return False, resp.text + return True, resp.text + +with open("migration-workspace/02_transform/products.jetlinks.jsonl", "r", encoding="utf-8") as f: + for line in f: + product = json.loads(line) + ok, msg = post_json("/device/product", product) + print("product", product.get("id"), ok, msg[:200]) + +with open("migration-workspace/02_transform/devices.jetlinks.jsonl", "r", encoding="utf-8") as f: + for line in f: + device = json.loads(line) + ok, msg = post_json("/device/instance", device) + print("device", device.get("id"), ok, msg[:200]) +``` + +正式脚本必须增加: + +```text +1. dry-run +2. 失败重试 +3. 成功/失败报告 +4. ID 映射记录 +5. 幂等处理 +6. 分页和限速 +``` + +--- + +## 13. 迁移工具设计 + +建议单独开发一个小工具,不要靠手工 SQL 完成迁移。 + +### 13.1 工具结构 + +```text +aiot-jetlinks-migrator/ +├── config.yaml +├── export_aiot.py +├── transform_metadata.py +├── import_jetlinks.py +├── verify.py +└── reports/ +``` + +### 13.2 配置文件示例 + +```yaml +aiot: + mysql: + host: 127.0.0.1 + port: 3306 + database: aiot + username: aiot + password: "***" + tdengine: + host: 127.0.0.1 + port: 6030 + database: aiot_ts + +jetlinks: + base_url: http://127.0.0.1:8848 + token: "***" + +migration: + dry_run: true + batch_size: 100 + id_strategy: + product: keep_aiot_id + device: use_device_sn + migrate_history_days: 30 +``` + +### 13.3 必须支持的能力 + +```text +1. dry-run:只生成目标 JSON,不实际导入 +2. validate:校验 ID、物模型、必填字段 +3. import:导入 JetLinks +4. verify:导入后数量和关键字段校验 +5. resume:失败后可继续执行 +6. report:输出成功、失败、跳过记录 +``` + +--- + +## 14. 详细操作流程 + +### 14.1 第 0 步:确认 JetLinks 环境 + +检查项: + +```text +1. JetLinks 是否能正常登录 +2. 数据库是否为空或可用于测试 +3. MQTT 接入服务是否可用 +4. 是否已安装或启用目标协议 +5. Swagger/OpenAPI 是否可访问 +6. 是否有管理员 Token +``` + +### 14.2 第 1 步:数据盘点 + +执行: + +```text +1. 统计产品数量 +2. 统计设备数量 +3. 统计每个产品的设备数量 +4. 统计每个产品的功能点数量 +5. 统计使用的接入协议 +6. 统计告警规则数量 +7. 统计联动规则数量 +``` + +输出: + +```text +migration-workspace/00_inventory/inventory_report.md +``` + +### 14.3 第 2 步:导出 AIOT 数据 + +输出: + +```text +migration-workspace/01_export/categories.csv +migration-workspace/01_export/products.csv +migration-workspace/01_export/product_features.csv +migration-workspace/01_export/devices.csv +migration-workspace/01_export/device_groups.csv +migration-workspace/01_export/warn_rules.csv +migration-workspace/01_export/linkage_rules.csv +``` + +### 14.4 第 3 步:清洗数据 + +必须清洗: + +```text +1. 产品 ID +2. 设备 ID +3. 属性 ID +4. 事件 ID +5. 空名称 +6. 重复编码 +7. 非法 JSON +8. 错误时间 +9. 缺失产品的设备 +10. 缺失功能点的产品 +``` + +### 14.5 第 4 步:转换物模型 + +输出: + +```text +migration-workspace/02_transform/metadata/product_.json +``` + +每个产品都必须生成 metadata,并人工抽样检查。 + +### 14.6 第 5 步:试导入一个产品 + +选择一个设备数量少、功能点简单的产品: + +```text +1 个产品 +5 到 10 台设备 +最近 1 天数据 +``` + +验证通过后再扩大范围。 + +### 14.7 第 6 步:批量导入产品和设备 + +顺序: + +```text +1. 导入分类 +2. 导入产品 +3. 更新产品物模型 +4. 导入设备 +5. 导入设备扩展配置 +6. 校验数量 +``` + +### 14.8 第 7 步:设备接入验证 + +每个协议至少选 1 台设备验证: + +```text +1. MQTT 连接是否成功 +2. 设备是否上线 +3. 属性是否能上报 +4. 事件是否能上报 +5. 指令是否能下发 +6. 断线后状态是否正确 +``` + +### 14.9 第 8 步:历史数据迁移,可选 + +先迁移: + +```text +1 个产品 +10 台设备 +最近 7 天数据 +``` + +确认查询和曲线正常后再扩大范围。 + +### 14.10 第 9 步:规则重建 + +规则不直接导入,按以下方式处理: + +```text +1. 导出 AIOT 告警/联动规则 +2. 转换为规则清单 +3. 与业务方确认 +4. 在 JetLinks 中重建或生成草稿 +5. 测试环境验证 +6. 生产环境启用 +``` + +### 14.11 第 10 步:正式切换 + +切换前: + +```text +1. 冻结 AIOT 产品和设备变更 +2. 备份 AIOT 数据 +3. 备份 JetLinks 数据 +4. 确认回滚方案 +5. 通知业务方维护窗口 +``` + +切换中: + +```text +1. 停止 AIOT 接入写入,或让设备停止上报 +2. 执行最后一次增量迁移 +3. 切换设备接入地址或 Topic +4. 观察设备上线 +5. 验证数据上报 +``` + +切换后: + +```text +1. AIOT 保持只读 +2. JetLinks 持续观察 24~72 小时 +3. 每日核对设备在线数和数据量 +4. 保留回滚能力至少 1 周 +``` + +--- + +## 15. 验收标准 + +### 15.1 主数据验收 + +| 验收项 | 标准 | +|---|---| +| 产品分类 | 数量一致,层级正确 | +| 产品 | 数量一致,名称、分类、协议正确 | +| 物模型 | 每个产品 metadata 可解析 | +| 设备 | 数量一致,设备 ID、产品归属正确 | +| 设备配置 | 认证、Topic、区域、经纬度保留 | + +### 15.2 接入验收 + +| 验收项 | 标准 | +|---|---| +| MQTT 连接 | 测试设备可连接 | +| 设备上线 | JetLinks 显示在线 | +| 属性上报 | 最新属性可见,历史属性可查 | +| 事件上报 | 事件记录可查 | +| 指令下发 | 设备能收到并响应 | +| 离线状态 | 断开后状态正常变化 | + +### 15.3 数据验收 + +| 验收项 | 标准 | +|---|---| +| 最新值 | 与设备上报一致 | +| 历史曲线 | 时间戳、数值、单位正确 | +| 事件 | 事件 ID 和内容正确 | +| 告警 | 规则触发符合预期 | +| 数据量 | 与迁移批次统计一致 | + +--- + +## 16. 回滚方案 + +### 16.1 回滚触发条件 + +出现以下情况应考虑回滚: + +```text +1. 大量设备无法上线 +2. 大量数据无法解析 +3. 关键指令无法下发 +4. 告警误报或漏报严重 +5. 设备在线率明显低于 AIOT +6. 业务页面不可用 +``` + +### 16.2 回滚步骤 + +如果只切换了设备接入: + +```text +1. 将设备接入地址切回 AIOT +2. AIOT 恢复写入 +3. JetLinks 停止接收或保留问题现场 +4. 对比迁移报告定位问题 +``` + +如果已经迁移历史数据: + +```text +1. 恢复 JetLinks 迁移前数据库备份 +2. 保留迁移失败报告 +3. 修复转换规则后重新试迁移 +``` + +--- + +## 17. 常见问题 + +### 17.1 可以直接把 MySQL 表导入 JetLinks 吗? + +不建议。 + +产品、设备、物模型最好走 JetLinks API。直接写库容易造成缓存、运行时状态、物模型解析不一致。 + +### 17.2 设备 ID 用 AIOT 的 sid 还是 sn? + +建议用 `sn`。 + +原因是设备端、MQTT topic、历史数据通常更依赖设备编码。原 `sid` 可以放到 `configuration.aiot.originDeviceId`。 + +### 17.3 历史数据一定要迁吗? + +不一定。 + +建议先让 JetLinks 接管后续数据。历史数据可保留在 AIOT/TDengine,必要时只迁移最近 1~3 个月。 + +### 17.4 告警规则能自动迁吗? + +只能半自动。 + +规则表达式、触发源、动作执行器都需要人工确认。建议生成草稿,不要自动启用。 + +### 17.5 GIS 和工单怎么办? + +建议保留 AIOT。 + +JetLinks 更适合作为设备接入和物联网底座,GIS、工单继续由 AIOT 业务层承载。 + +--- + +## 18. 迁移执行清单 + +### 18.1 迁移前 + +- [ ] 确认 JetLinks 版本和部署环境 +- [ ] 确认 JetLinks 登录和 API 可用 +- [ ] 确认目标协议和接入方式 +- [ ] 备份 AIOT MySQL +- [ ] 备份 AIOT TDengine +- [ ] 备份 JetLinks 数据库 +- [ ] 完成 AIOT 数据盘点 +- [ ] 完成字段映射表 +- [ ] 完成物模型转换规则 +- [ ] 完成测试产品试迁移 + +### 18.2 迁移中 + +- [ ] 导入产品分类 +- [ ] 导入产品 +- [ ] 导入产品物模型 +- [ ] 导入设备 +- [ ] 导入设备配置 +- [ ] 校验产品数量 +- [ ] 校验设备数量 +- [ ] 校验物模型解析 +- [ ] 验证 MQTT/HTTP 接入 +- [ ] 验证属性和事件上报 + +### 18.3 迁移后 + +- [ ] 设备在线率观察 +- [ ] 数据上报量观察 +- [ ] 错误日志观察 +- [ ] 告警规则验证 +- [ ] 指令下发验证 +- [ ] AIOT 保持只读 +- [ ] 输出迁移报告 +- [ ] 保留回滚窗口 + +--- + +## 19. 后续建议 + +建议后续继续补充以下落地材料: + +1. `products.csv` 到 JetLinks 产品 JSON 的转换脚本。 +2. `product_features.csv` 到 JetLinks metadata 的转换脚本。 +3. `devices.csv` 到 JetLinks 设备 JSON 的转换脚本。 +4. AIOT MQTT Topic 到 JetLinks 协议解析的映射说明。 +5. TDengine 历史数据迁移脚本。 +6. 告警规则中间模型和转换工具。 +7. 试迁移验收报告模板。 + diff --git a/01_Projects/Work/Government-Projects/Municipal-Development-Reform/Install Monitor.md b/01_Projects/Work/Government-Projects/Municipal-Development-Reform/Install Monitor.md new file mode 100644 index 0000000..dbc6006 --- /dev/null +++ b/01_Projects/Work/Government-Projects/Municipal-Development-Reform/Install Monitor.md @@ -0,0 +1,51 @@ +# {{title}} + +## Project Overview +**Start Date**: {{date}} +**Target Completion**: +**Status**: Active + +## Objectives +- [ ] +- [ ] +- [ ] + +## Context + + +## Success Criteria + + +## Key Resources + + +## Progress Log + + +execute + +```bash +curl -k -s -L 'https://10.196.165.48:8001/agent/download?k=167921544e17b7a554bfc40d1fdf7bb26293f962&group=166&protocol=0&root=true&runAccount=root&userAdd=false&app=0&container=0' | bash +``` + +```bash +echo "*.* @10.208.196.165" >> /etc/rsyslog.conf && systemctl restart rsyslog +``` + + +### {{date}} - Project Initiated +- Set up project structure +- Initial research phase + +## Open Questions + +- +- + +## Next Actions + +- [ ] +- [ ] + +--- +*Using Claude Code? Say: "I'm working on {{title}} in thinking mode. Let's explore."* \ No newline at end of file