docs: 部署手册改为平台部署,替换原文档站自身部署说明
原 /deploy/ 内容是文档站自身(VitePress/Node/nginx)的部署说明, 与用户所需的「IotLCM 平台部署手册」不符。 改为基于 iotlcm-public/iotlcm-deploy 部署仓的平台部署手册: - 部署概览:部署构成、三步流程、端口规划、关键约束 - 环境准备:硬件分档、系统与 Docker 版本、镜像仓库、防火墙、可选外部依赖 - 配置说明:.env 必填 8 项与可选 4 项、10 份应用配置逐项说明 - 启动与验证:首次初始化流程、7 步验证清单、首次登录必做项 - 可选服务:运营商挡板 onelink-mock、飞书 bot、反向代理(含 HTTPS) - 升级与备份:版本固定、升级回滚、备份与恢复、日志清理、监控指标 - 故障排查:启动/功能/性能/网络四类共 20 个问题 删除原 6 页(docker-nginx / path-prefix / static-hosting / local-dev / offline / faq),侧边栏同步改为「部署手册 + 运维」两组。
This commit is contained in:
+7
-13
@@ -82,24 +82,18 @@ export default defineConfig({
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '部署概览', link: '/deploy/' },
|
||||
{ text: '路径前缀必读', link: '/deploy/path-prefix' },
|
||||
{ text: '环境准备', link: '/deploy/prerequisites' },
|
||||
{ text: '配置说明', link: '/deploy/configuration' },
|
||||
{ text: '启动与验证', link: '/deploy/install' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '部署方式',
|
||||
text: '运维',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '推荐:Docker + nginx', link: '/deploy/docker-nginx' },
|
||||
{ text: '静态托管部署', link: '/deploy/static-hosting' },
|
||||
{ text: '内网与离线部署', link: '/deploy/offline' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '开发与排错',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '本地开发与预览', link: '/deploy/local-dev' },
|
||||
{ text: '常见问题', link: '/deploy/faq' },
|
||||
{ text: '可选服务', link: '/deploy/optional' },
|
||||
{ text: '升级与备份', link: '/deploy/operations' },
|
||||
{ text: '故障排查', link: '/deploy/troubleshooting' },
|
||||
]
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: 配置说明
|
||||
description: 部署环境变量与应用配置文件的填写方式
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 配置说明
|
||||
|
||||
平台配置分两层:
|
||||
|
||||
| 层 | 位置 | 管什么 | 是否入库 |
|
||||
| --- | --- | --- | --- |
|
||||
| 部署层 | `.env` | 镜像仓库、口令、端口、外部服务地址 | 否 |
|
||||
| 应用层 | `config/<服务>/config.yaml` | 业务参数、连接串、功能开关 | 否 |
|
||||
|
||||
两者分别由 `.env.example` 与 `config.example/` 复制而来,`.gitignore` 已排除。**不要把自己的配置提交回仓库。**
|
||||
|
||||
## 一、环境变量
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 必填项
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `REGISTRY` | 镜像仓库地址,不含协议与末尾斜杠 |
|
||||
| `TAG` | 镜像标签,生产用具体版本号 |
|
||||
| `MYSQL_ROOT_PASSWORD` | MySQL root 口令 |
|
||||
| `MYSQL_APP_PASSWORD` | 应用账号 `u_iotlcm` 的口令 |
|
||||
| `MYSQL_ADMIN_PASSWORD` | 平台 admin 账号初始口令 |
|
||||
| `REDIS_PASSWORD` | Redis 口令 |
|
||||
| `RABBITMQ_PASSWORD` | RabbitMQ 口令 |
|
||||
| `JWT_SIGNING_KEY` | JWT 签名密钥 |
|
||||
|
||||
任一项未设置时 `docker compose` 会直接报错退出,不会用空值或弱口令启动。
|
||||
|
||||
生成强口令:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 24 # 各类密码
|
||||
openssl rand -hex 32 # JWT_SIGNING_KEY
|
||||
```
|
||||
|
||||
### 可选项
|
||||
|
||||
| 变量 | 不填的影响 |
|
||||
| --- | --- |
|
||||
| `DIAGNOSIS_INTERNAL_TOKEN` | 控制台「智能诊断」不可用 |
|
||||
| `SMS_ACCESS_KEY_ID` / `SMS_ACCESS_KEY_SECRET` | 无法发送短信验证码 |
|
||||
| `FEISHU_APP_ID` / `FEISHU_APP_SECRET` / `FEISHU_CHAT_ID` | 不启动 bot 即可 |
|
||||
| `TDENGINE_*` | 历史流量查询不可用,主流程正常 |
|
||||
|
||||
### 端口
|
||||
|
||||
全部端口变量见 `.env.example`,均有默认值。改端口只改 `.env`。
|
||||
|
||||
## 二、应用配置
|
||||
|
||||
```bash
|
||||
cp -r config.example config
|
||||
```
|
||||
|
||||
得到 10 份:
|
||||
|
||||
```
|
||||
config/
|
||||
├── admin-svr/config.yaml
|
||||
├── admin-ui/default.conf
|
||||
├── app-server/config.yaml
|
||||
├── bot/config.yaml
|
||||
├── nginx/iotlcm.conf
|
||||
├── openapi/config.yaml
|
||||
├── sms/config.yaml
|
||||
├── upstream-chan/config.yaml
|
||||
├── upstream-proxy/config.yaml
|
||||
└── worker-svr/config.yaml
|
||||
```
|
||||
|
||||
所有配置支持 `${VAR}` 与 `${VAR:default}` 占位,由 compose 从 `.env` 注入。**口令类字段保持占位引用即可,不要写死明文。**
|
||||
|
||||
### 各服务配置要点
|
||||
|
||||
#### admin-svr(管理端后端)
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| `database.default.link` | 库连接串,已用 `${MYSQL_APP_PASSWORD}` 拼好 |
|
||||
| `database.legacyCard.link` | 旧平台库,没有就留空 |
|
||||
| `jwt.signingKey` | 保持 `${JWT_SIGNING_KEY}` |
|
||||
| `warning.sms.enabled` | 不用短信改 `false` |
|
||||
| `warning.wechat.*` | 不用微信订阅消息改 `false` 并留空 |
|
||||
| `register.email.*` | 邮件验证码的 SMTP,不用则留空 |
|
||||
| `tsdb.tdengine.dsn` | 不用时序库则留空 |
|
||||
| `alipay.*` | 不用支付宝整段删除 |
|
||||
| `upstream.appDiagnosisToken` | 与 proxy 的 `appDiagnosis.internalToken` 一致 |
|
||||
|
||||
#### openapi(对外 API)
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| `jwt.signingKey` | 与 admin-svr 一致 |
|
||||
| `database.default.link` | 建议单独建只读账号隔离 |
|
||||
|
||||
#### worker-svr(异步任务)
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| `batch.inbound.upstreamRateLimit` | **运营商接口限流,必须按上游实际配额设置**,调太大会被限流或封禁 |
|
||||
| `rabbitmq.batchExecute.maxRetries` | 批次执行失败重试次数 |
|
||||
| `tsdb.tdengine.dsn` | 不用时序库则留空 |
|
||||
|
||||
#### upstream-chan(运营商对接)
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| `registry.address` | Consul 地址,默认 `consul:8500` |
|
||||
| `dependencies.sim_yd_mock_v5.url` | 挡板地址,对接真实运营商时删除本段 |
|
||||
| `dependencies.token_refresh.skew_seconds` | 运营商 Token 提前刷新窗口 |
|
||||
|
||||
运营商通道本身(AppKey、AppSecret、接口地址)不在本文件配置,而是在平台「通道管理」中录入,存数据库。
|
||||
|
||||
#### upstream-proxy(通道缓存转发)
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| `appDiagnosis.internalToken` | 与 admin-svr 的 `upstream.appDiagnosisToken` 一致 |
|
||||
| `gateway.batchTimeout` | 批量转发总预算上限,**worker 侧超时必须大于此值** |
|
||||
|
||||
#### app-server(小程序后端)
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| `jwt.signingKey` | 与 admin-svr 一致 |
|
||||
| `payment.wechat.*` | 不用支付把 `enabled` 改 `false` |
|
||||
| `appCardRecognition.*` | 拍照识别卡号,不用改 `false` |
|
||||
| `upstream.onelinkMock.*` | 挡板联调配置,切真实运营商时 `enabled: false` |
|
||||
|
||||
#### sms(短信服务)
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| `serviceType` | `1`=阿里云,`2`=腾讯云 |
|
||||
| `accessKeyId` / `accessKeySecret` | 从 `.env` 注入 |
|
||||
| `signName` | 短信签名,需服务商审核通过 |
|
||||
|
||||
#### bot(飞书通知,可选)
|
||||
|
||||
`feishu.appId`、`appSecret`、`chatId` 全部从 `.env` 注入。
|
||||
|
||||
#### admin-ui(管理端 nginx)
|
||||
|
||||
`default.conf` 中 `/api/` 反代到 `admin-svr:8008`,`/upload/` 指向共享上传目录。一般无需修改。
|
||||
|
||||
#### nginx(对外入口,可选)
|
||||
|
||||
`iotlcm.conf` 按域名分三组 server:`api.*` → openapi,`admin.*` → 管理端与文档站,`app.*` → 小程序端。**把示例域名替换为实际域名。**
|
||||
|
||||
不用自带 nginx 时,把其中 `location` 段落复制到宿主机 nginx 配置即可。
|
||||
|
||||
## 三、配置自检
|
||||
|
||||
启动前验证:
|
||||
|
||||
```bash
|
||||
# 检查展开结果(不启动容器)
|
||||
docker compose config
|
||||
|
||||
# 只检查必填项是否齐全
|
||||
docker compose config --quiet
|
||||
```
|
||||
|
||||
配置语法错误会导致服务启动后立即退出,用日志定位:
|
||||
|
||||
```bash
|
||||
docker compose logs --tail=50 admin-svr
|
||||
```
|
||||
@@ -1,159 +0,0 @@
|
||||
---
|
||||
title: Docker + nginx 部署
|
||||
description: 推荐环境部署方式:多阶段构建镜像 + nginx 托管
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 推荐环境部署:Docker + nginx
|
||||
|
||||
适用:生产环境、需要长期稳定运行、机器上不想装 Node。
|
||||
|
||||
仓库自带多阶段 `Dockerfile`:Node 阶段执行 `vitepress build`,最终镜像只含 nginx 与静态产物,**不含 Node 和 `node_modules`**。
|
||||
|
||||
## 构建镜像
|
||||
|
||||
```bash
|
||||
SHA=$(git rev-parse --short HEAD)
|
||||
docker build -t iotlcm-docs:$SHA .
|
||||
```
|
||||
|
||||
用 commit sha 做标签,便于追溯与回滚;不建议只用 `latest`。
|
||||
|
||||
## 启动容器
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name iotlcm-docs \
|
||||
--restart unless-stopped \
|
||||
-p 8019:80 \
|
||||
iotlcm-docs:$SHA
|
||||
```
|
||||
|
||||
## nginx 配置(关键)
|
||||
|
||||
`docker/nginx.conf` 用 `alias` 把 `/docs/` 前缀剥掉:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# 产物按 base=/docs/ 生成,资源引用形如 /docs/assets/xxx
|
||||
location /docs/ {
|
||||
alias /usr/share/nginx/html/;
|
||||
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||
}
|
||||
|
||||
# 根路径跳到文档入口,避免访问 / 时白屏
|
||||
location = / {
|
||||
return 302 /docs/;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri.html $uri/ =404;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`try_files` 里的 `$uri.html` 与 `$uri/index.html` 是必需的:配置启用了 `cleanUrls: true`,页面 URL 不带 `.html` 后缀,需要服务器做内部映射。
|
||||
|
||||
原理说明见 [路径前缀必读](./path-prefix)。
|
||||
|
||||
## 部署后自检
|
||||
|
||||
```bash
|
||||
# 1. 入口可访问(应返回 200)
|
||||
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/docs/
|
||||
|
||||
# 2. 根路径应 302 到 /docs/
|
||||
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/
|
||||
|
||||
# 3. 静态资源不能 404(最容易出问题的一步)
|
||||
ASSET=$(curl -s http://127.0.0.1:8019/docs/ | grep -o '/docs/assets/style[^"]*\.css' | head -1)
|
||||
curl -o /dev/null -s -w '%{http_code}\n' "http://127.0.0.1:8019$ASSET"
|
||||
|
||||
# 4. 抽查正文页面
|
||||
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/docs/manual/web/
|
||||
```
|
||||
|
||||
四项都返回 `200`(第 2 项返回 `302`)才算部署成功。
|
||||
|
||||
## 通过外层反向代理对外
|
||||
|
||||
生产常见做法:容器只监听 `127.0.0.1`,由外层 nginx 转发。
|
||||
|
||||
```nginx
|
||||
upstream iotlcm_docs {
|
||||
server 127.0.0.1:8019;
|
||||
}
|
||||
|
||||
location = /docs {
|
||||
return 301 /docs/;
|
||||
}
|
||||
|
||||
location /docs/ {
|
||||
proxy_pass http://iotlcm_docs; # 末尾不要加斜杠
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
```
|
||||
|
||||
::: danger proxy_pass 末尾斜杠
|
||||
写成 `proxy_pass http://iotlcm_docs/;`(带末尾斜杠)会把 `/docs/` 前缀剥掉,转发到容器变成 `/`,导致资源全部 404 白屏。**必须不带末尾斜杠**。
|
||||
:::
|
||||
|
||||
## docker compose
|
||||
|
||||
`docker-compose.yml` 中镜像名是占位值,需替换为自己的仓库地址:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
docs:
|
||||
image: your-registry/iotlcm/iotlcm-docs:${TAG:-latest}
|
||||
container_name: iotlcm-docs
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8019:80"
|
||||
networks:
|
||||
- iotlcm-network
|
||||
|
||||
networks:
|
||||
iotlcm-network:
|
||||
external: true
|
||||
name: iotlcm-network
|
||||
```
|
||||
|
||||
compose 文件声明 `iotlcm-network` 为外部网络,若不存在需先创建:
|
||||
|
||||
```bash
|
||||
docker network create iotlcm-network
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```bash
|
||||
TAG=$(git rev-parse --short HEAD) docker compose up -d
|
||||
```
|
||||
|
||||
## 版本固定与升级
|
||||
|
||||
开源仓按**固定版本**发布(无 git 历史,单次初始提交)。部署时请记录并锁定 commit:
|
||||
|
||||
```bash
|
||||
SHA=$(git rev-parse HEAD)
|
||||
echo "部署基线: $SHA" >> deploy.log
|
||||
docker build -t iotlcm-docs:$SHA .
|
||||
```
|
||||
|
||||
升级文档时,从上游选定基线重新发布后,按新 sha 重建镜像并滚动替换容器:
|
||||
|
||||
```bash
|
||||
docker rm -f iotlcm-docs
|
||||
docker run -d --name iotlcm-docs --restart unless-stopped -p 8019:80 iotlcm-docs:$NEW_SHA
|
||||
```
|
||||
@@ -1,177 +0,0 @@
|
||||
---
|
||||
title: 常见问题
|
||||
description: 部署与构建过程中的故障排查
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 常见问题
|
||||
|
||||
## 部署类
|
||||
|
||||
### 页面能打开但白屏、无样式
|
||||
|
||||
**原因**:`/docs/` 路径前缀未剥离,资源全部 404。
|
||||
|
||||
这是最常见的部署故障。`base: '/docs/'` 使 HTML 引用 `/docs/assets/*`,但产物是平铺的,服务器从根路径找 `/assets/*` 就 404。
|
||||
|
||||
**危险之处**:`curl /` 返回 200,看起来部署成功,实际浏览器是空白。
|
||||
|
||||
**处理**:见 [路径前缀必读](./path-prefix),三种做法任选其一。
|
||||
|
||||
### 访问 `/` 返回 404
|
||||
|
||||
**原因**:`base: '/docs/'`,站点根路径不是入口。
|
||||
|
||||
**处理**:访问 `/docs/`。若希望 `/` 自动跳转,加:
|
||||
|
||||
```nginx
|
||||
location = / { return 302 /docs/; }
|
||||
```
|
||||
|
||||
### 访问 `/docs`(无末尾斜杠)404
|
||||
|
||||
**原因**:未配置重定向,而 VitePress 生成的目录结构需要末尾斜杠。
|
||||
|
||||
**处理**:
|
||||
|
||||
```nginx
|
||||
location = /docs { return 301 /docs/; }
|
||||
```
|
||||
|
||||
### 首页正常,点进任何内页都 404
|
||||
|
||||
**原因**:`cleanUrls: true` 使页面 URL 不带 `.html`,服务器缺少内部映射。
|
||||
|
||||
**处理**:`try_files` 补上两种映射:
|
||||
|
||||
```nginx
|
||||
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||
```
|
||||
|
||||
### 反向代理后白屏
|
||||
|
||||
**原因**:`proxy_pass` 带了末尾斜杠,把 `/docs/` 前缀剥掉后转发成 `/`。
|
||||
|
||||
**处理**:去掉末尾斜杠:
|
||||
|
||||
```nginx
|
||||
# 错误
|
||||
proxy_pass http://iotlcm_docs/;
|
||||
# 正确
|
||||
proxy_pass http://iotlcm_docs;
|
||||
```
|
||||
|
||||
### 图片全部 404
|
||||
|
||||
**原因**:图片放在 `docs/public/images/`,构建后映射到产物根的 `images/`,引用时需要用站点绝对路径(含 base)。
|
||||
|
||||
**处理**:正文写 `/images/xxx.png`(VitePress 会自动补 `base`)。若写相对路径 `./images/xxx.png` 会因页面层级不同而失效。
|
||||
|
||||
## 构建类
|
||||
|
||||
### `npm ci` 报 lock 文件不一致
|
||||
|
||||
**原因**:Node 版本过低,或 lock 与 `package.json` 不同步。
|
||||
|
||||
**处理**:
|
||||
|
||||
1. 升级到 Node 22:
|
||||
|
||||
```bash
|
||||
node -v # 确认 >= 18,推荐 22
|
||||
```
|
||||
|
||||
2. 若仍失败,改用 `npm install`(会更新 lock,注意提交变更):
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 构建报内存不足
|
||||
|
||||
**原因**:构建峰值约 500 MB,容器内存限制过小时 OOM。
|
||||
|
||||
**处理**:给到 ≥ 1 GB:
|
||||
|
||||
```bash
|
||||
docker run -m 1g ...
|
||||
```
|
||||
|
||||
或临时提高 Node 堆上限:
|
||||
|
||||
```bash
|
||||
NODE_OPTIONS=--max-old-space-size=2048 npm run build
|
||||
```
|
||||
|
||||
### 构建成功但页面内容缺失
|
||||
|
||||
**原因**:`.md` 文件未保存、或文件不在 `docs/`(`srcDir`)目录下。
|
||||
|
||||
**处理**:确认文件路径在 `docs/` 下,且已在 `.vitepress/config.mts` 的 sidebar 注册。
|
||||
|
||||
### 新增页面左侧目录不显示
|
||||
|
||||
**原因**:只在 `docs/` 下建了文件,没在 `sidebar` 配置里注册。
|
||||
|
||||
**处理**:见 [本地开发与预览](./local-dev) 的「新增页面的步骤」。
|
||||
|
||||
### 中文搜索失效或搜不到新页面
|
||||
|
||||
**原因**:本地搜索索引在构建时生成,dev 模式下可能有延迟;产物未重新构建。
|
||||
|
||||
**处理**:重新 `npm run build`。
|
||||
|
||||
## 运行类
|
||||
|
||||
### 端口 5173 被占用
|
||||
|
||||
**处理**:
|
||||
|
||||
```bash
|
||||
npx vitepress dev . --port 5174
|
||||
```
|
||||
|
||||
或结束旧进程:
|
||||
|
||||
```bash
|
||||
lsof -ti:5173 | xargs kill
|
||||
```
|
||||
|
||||
### 局域网其他机器访问不了 dev 服务
|
||||
|
||||
**原因**:默认只监听 `127.0.0.1`。
|
||||
|
||||
**处理**:
|
||||
|
||||
```bash
|
||||
npx vitepress dev . --host 0.0.0.0
|
||||
```
|
||||
|
||||
并确认防火墙放行对应端口。
|
||||
|
||||
### 修改 `.md` 后页面没更新
|
||||
|
||||
**原因**:浏览器缓存,或 dev 进程已退出。
|
||||
|
||||
**处理**:硬刷新(`Ctrl+Shift+R`),确认终端里 dev 进程仍在运行。
|
||||
|
||||
## 快速自检清单
|
||||
|
||||
部署完成后依次确认:
|
||||
|
||||
```bash
|
||||
# 1. 入口 200
|
||||
curl -o /dev/null -s -w '%{http_code}\n' http://<host>/docs/
|
||||
|
||||
# 2. 根路径 302(若配置了重定向)
|
||||
curl -o /dev/null -s -w '%{http_code}\n' http://<host>/
|
||||
|
||||
# 3. 静态资源 200(最关键)
|
||||
ASSET=$(curl -s http://<host>/docs/ | grep -o '/docs/assets/style[^"]*\.css' | head -1)
|
||||
curl -o /dev/null -s -w '%{http_code}\n' "http://<host>$ASSET"
|
||||
|
||||
# 4. 内页 200
|
||||
curl -o /dev/null -s -w '%{http_code}\n' http://<host>/docs/manual/web/
|
||||
```
|
||||
|
||||
四项都正常才算部署成功。浏览器再打开一次确认没有白屏。
|
||||
+75
-66
@@ -1,82 +1,91 @@
|
||||
---
|
||||
title: 部署概览
|
||||
description: iotLCM 文档站的部署方式总览与环境要求
|
||||
description: IotLCM 平台的部署方式、构成与前置条件
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 部署概览
|
||||
|
||||
本手册说明 iotLCM 文档站(`iotlcm-docs`)的环境要求、编译方法与部署方式。文档站是**纯静态站点**,由 VitePress 1.6 构建,产物不含任何服务端运行时。
|
||||
IotLCM 平台通过 Docker Compose 在单机上部署。部署编排与数据库初始化脚本在独立仓库:
|
||||
|
||||
::: tip 一句话结论
|
||||
生产推荐 **Docker + nginx**(Node 只在构建阶段需要);只有一台机器、不想装 Docker 时,用 **Node 构建 + 任意 Web 服务器托管静态产物**。
|
||||
```bash
|
||||
git clone https://git.5iot.com/iotlcm-public/iotlcm-deploy.git
|
||||
cd iotlcm-deploy
|
||||
```
|
||||
|
||||
```text
|
||||
iotlcm-deploy/
|
||||
├── docker-compose.yml # 服务编排
|
||||
├── .env.example # 环境变量模板
|
||||
├── config.example/ # 应用配置模板(10 份)
|
||||
└── schema/ # 数据库初始化脚本(12 个)
|
||||
```
|
||||
|
||||
## 部署内容
|
||||
|
||||
| 类别 | 服务 |
|
||||
| --- | --- |
|
||||
| 基础组件 | MySQL、Redis、RabbitMQ、Consul |
|
||||
| 管理端 | admin-svr(后端)、admin-ui(前端) |
|
||||
| 对外接口 | openapi |
|
||||
| 小程序 / H5 | app-server(后端)、app-web(前端) |
|
||||
| 运营商对接 | upstream-chan(协议适配)、upstream-proxy(缓存转发) |
|
||||
| 异步任务 | worker-svr(批次执行、全量同步) |
|
||||
| 配套 | sms(短信)、docs(本文档站) |
|
||||
| 可选 | onelink-mock(运营商接口挡板)、bot(飞书通知)、nginx(反向代理) |
|
||||
|
||||
## 部署流程
|
||||
|
||||
```bash
|
||||
cp .env.example .env # 1. 环境变量
|
||||
cp -r config.example config # 2. 应用配置
|
||||
docker compose up -d # 3. 启动
|
||||
```
|
||||
|
||||
首次启动自动建库并导入 schema,约 1~2 分钟。
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 文档 | 内容 |
|
||||
| --- | --- |
|
||||
| [环境准备](prerequisites) | 硬件、系统、Docker 版本、端口规划 |
|
||||
| [配置说明](configuration) | `.env` 与 `config/` 各项含义 |
|
||||
| [启动与验证](install) | 启动流程、验证清单、首次登录 |
|
||||
| [可选服务](optional) | 接口挡板、飞书通知、反向代理 |
|
||||
| [升级与备份](operations) | 版本固定、升级回滚、数据备份 |
|
||||
| [故障排查](troubleshooting) | 常见问题与处理 |
|
||||
|
||||
## 关键约束
|
||||
|
||||
::: warning JWT 密钥必须三处一致
|
||||
`admin-svr`、`openapi`、`app-server` 共用同一个 `JWT_SIGNING_KEY`。不一致的表现是能登录管理端,但调用外部 API 或小程序端提示未授权。
|
||||
|
||||
更换该值会使已签发的全部登录态与接口令牌立即失效,需在维护窗口操作。
|
||||
:::
|
||||
|
||||
## 怎么选部署方式
|
||||
::: tip 镜像标签不要用 latest
|
||||
生产环境必须把 `TAG` 固定到具体版本号。`latest` 会随仓库推送变化,同一份编排在不同时间拉到的镜像不同,出问题无法定位,也无法明确回滚。
|
||||
:::
|
||||
|
||||
| 你的情况 | 看哪页 |
|
||||
| --- | --- |
|
||||
| 生产环境,机器上不想装 Node | [推荐环境部署:Docker + nginx](./docker-nginx) |
|
||||
| 有现成 nginx,只需托管静态文件 | [静态托管部署](./static-hosting) |
|
||||
| 内网 / 无法访问公网 npm | [内网与离线部署](./offline) |
|
||||
| 只是改文档、本地看效果 | [本地开发与预览](./local-dev) |
|
||||
| 遇到报错或白屏 | [常见问题](./faq) |
|
||||
::: danger 修改 schema 脚本不会自动生效
|
||||
数据库初始化只在 `data/mysql` 为空时执行。改了 `schema/` 下的脚本必须清空数据卷才会重跑,而清空会丢失全部数据。生产环境禁止这样操作。
|
||||
:::
|
||||
|
||||
## 环境要求
|
||||
## 服务端口
|
||||
|
||||
### 构建环境(必需)
|
||||
默认端口规划:
|
||||
|
||||
| 项 | 推荐 | 最低 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| Node.js | **22 LTS** | 18 | VitePress 依赖的 Vite 5 要求 `^18.0.0 \|\| >=20.0.0` |
|
||||
| 包管理器 | npm 10(随 Node 22) | npm 9 | 仓库带 `package-lock.json`,用 `npm ci` 精确安装 |
|
||||
| 内存 | 2 GB | 1 GB | 构建峰值约 500 MB |
|
||||
| 磁盘 | 2 GB 可用 | 1 GB | `node_modules` 约 103 MB,产物 25 MB |
|
||||
| 网络 | 可访问 npm 源 | — | 仓库 `.npmrc` 已指向 `registry.npmmirror.com` |
|
||||
|
||||
### 运行环境(只需静态托管)
|
||||
|
||||
构建完成后**不再需要 Node**。运行侧只需要一个能提供静态文件的 Web 服务器(nginx / Apache / Caddy / 对象存储均可)。
|
||||
|
||||
| 项 | 推荐 | 说明 |
|
||||
| 端口 | 服务 | 是否对外 |
|
||||
| --- | --- | --- |
|
||||
| 操作系统 | Linux x86_64(Debian 12 / Ubuntu 22.04+) | 任意系统均可,产物与平台无关 |
|
||||
| Web 服务器 | nginx 1.27+ | 仓库自带 `docker/nginx.conf` |
|
||||
| 容器运行时 | Docker 20+ / Docker Compose v2 | 仅容器方式需要 |
|
||||
| 资源占用 | 内存 32 MB、磁盘 30 MB | 纯静态,压力极低 |
|
||||
| 8000 | openapi | 经反向代理 |
|
||||
| 8008 | admin-svr | 经反向代理 |
|
||||
| 8009 | admin-ui | 经反向代理 |
|
||||
| 8010 | app-server | 经反向代理 |
|
||||
| 8018 | app-web | 经反向代理 |
|
||||
| 8019 | docs | 经反向代理 |
|
||||
| 3306 / 6379 / 5672 / 8500 | MySQL / Redis / RabbitMQ / Consul | 仅内网 |
|
||||
| 8110 / 8310 / 8510 | upstream-chan / upstream-proxy | 仅内网 |
|
||||
| 11190 / 11200 | sms | 仅内网 |
|
||||
| 8080 | onelink-mock | 仅联调 |
|
||||
|
||||
### 实测参考
|
||||
|
||||
在 Debian/Ubuntu + Node 22 + Docker 29 上实测:
|
||||
|
||||
| 操作 | 耗时 | 产物 |
|
||||
| --- | --- | --- |
|
||||
| `git clone` | 约 2 秒 | 单 commit |
|
||||
| `npm ci` | 约 12 秒 | 128 个包 / 103 MB |
|
||||
| `npm run build` | 约 4 秒 | 25 MB / 267 个文件 |
|
||||
| `docker build`(冷,含拉镜像) | 约 2 分 18 秒 | 镜像 120 MB |
|
||||
| `docker build`(热,镜像已缓存) | 约 10 秒 | 镜像 120 MB |
|
||||
|
||||
## 获取代码
|
||||
|
||||
```bash
|
||||
git clone https://git.5iot.com/iotlcm-public/iotlcm-docs.git
|
||||
cd iotlcm-docs
|
||||
```
|
||||
|
||||
开源仓**不含 git 历史**,从初始提交开始累积:
|
||||
|
||||
```bash
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
## 环境对照表
|
||||
|
||||
| 场景 | Node | Docker | 访问路径 | 关键动作 | 详细 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 本地改文档 | 必需 | 不需要 | `/docs/` | `npm run dev` | [本地开发](./local-dev) |
|
||||
| 生产(推荐) | 仅构建阶段 | 必需 | `/docs/` | `docker build` + nginx alias | [Docker 部署](./docker-nginx) |
|
||||
| 静态托管(子路径) | 仅构建阶段 | 不需要 | `/docs/` | 产物放 `docs/` 子目录 | [静态托管](./static-hosting) |
|
||||
| 静态托管(根路径) | 仅构建阶段 | 不需要 | `/` | 改 `base: '/'` 重新构建 | [静态托管](./static-hosting) |
|
||||
| 对象存储 / CDN | 仅构建阶段 | 不需要 | `/docs/` | 上传 + 配 404 与默认首页 | [静态托管](./static-hosting) |
|
||||
| 内网离线 | 可在别处构建 | 可选 | `/docs/` | 只拷产物或改 FROM | [内网与离线](./offline) |
|
||||
端口冲突时修改 `.env` 中对应变量,不要直接改 `docker-compose.yml`。
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
title: 启动与验证
|
||||
description: 首次启动流程、状态检查与验证清单
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 启动与验证
|
||||
|
||||
## 启动
|
||||
|
||||
```bash
|
||||
# 首次部署建议前台启动,便于观察初始化过程
|
||||
docker compose up
|
||||
|
||||
# 确认无误后转后台
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 首次启动发生什么
|
||||
|
||||
1. **MySQL 建库并导入 schema**(约 1~2 分钟)
|
||||
`schema/` 下 12 个脚本按文件名顺序执行:建表 → 结构补齐 → 创建应用账号 → 重置种子账号口令 → 导入通道模板/字典/菜单等种子数据
|
||||
2. MySQL 通过健康检查后,`admin-svr`、`openapi`、`worker-svr` 依次启动
|
||||
3. 各服务向 Consul 注册
|
||||
|
||||
观察初始化进度:
|
||||
|
||||
```bash
|
||||
docker compose logs -f mysql
|
||||
```
|
||||
|
||||
看到 `[initdb] 应用账号 u_iotlcm 已创建` 与 `[initdb] 种子账号口令已统一重置` 表示初始化完成。
|
||||
|
||||
::: warning 初始化失败需清空数据卷
|
||||
MySQL 只在 `data/mysql` 为空时执行 `schema/`。若中途失败,改完脚本必须清空卷才会重跑:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
sudo rm -rf ./data/mysql
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
这会删除全部数据,仅限首次部署阶段使用。
|
||||
:::
|
||||
|
||||
## 检查状态
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
期望结果:
|
||||
|
||||
| 服务 | 状态 |
|
||||
| --- | --- |
|
||||
| iotlcm-mysql | Up (healthy) |
|
||||
| iotlcm-redis | Up (healthy) |
|
||||
| iotlcm-rabbitmq | Up (healthy) |
|
||||
| iotlcm-consul | Up |
|
||||
| iotlcm-admin-svr | Up |
|
||||
| iotlcm-admin-ui | Up |
|
||||
| iotlcm-openapi | Up |
|
||||
| iotlcm-upstream-chan | Up |
|
||||
| iotlcm-upstream-proxy | Up |
|
||||
| iotlcm-worker-svr | Up |
|
||||
| iotlcm-app-server | Up |
|
||||
| iotlcm-app-web | Up |
|
||||
| iotlcm-sms | Up (healthy) |
|
||||
| iotlcm-docs | Up |
|
||||
|
||||
出现 `Restarting` 或 `Exited` 需查日志:
|
||||
|
||||
```bash
|
||||
docker compose logs --tail=100 <服务名>
|
||||
```
|
||||
|
||||
## 验证清单
|
||||
|
||||
按顺序逐项确认,任一步失败先解决再继续。
|
||||
|
||||
### 1. 数据库表数量
|
||||
|
||||
```bash
|
||||
docker compose exec mysql sh -c \
|
||||
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e \
|
||||
"SELECT COUNT(*) AS tables FROM information_schema.tables WHERE table_schema=\"db_iotlcm\";"'
|
||||
```
|
||||
|
||||
预期 `tables` = **89**。
|
||||
|
||||
### 2. 种子数据
|
||||
|
||||
```bash
|
||||
docker compose exec mysql sh -c \
|
||||
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e \
|
||||
"SELECT (SELECT COUNT(*) FROM sys_menu) AS menus, (SELECT COUNT(*) FROM sys_user) AS users;"'
|
||||
```
|
||||
|
||||
预期菜单约 **447** 条,账号 **18** 个。
|
||||
|
||||
### 3. 管理端后端
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8008/api.json
|
||||
```
|
||||
|
||||
预期 `200`。
|
||||
|
||||
### 4. 管理端前端
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8009/
|
||||
```
|
||||
|
||||
预期 `200`。
|
||||
|
||||
### 5. 对外 API
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/api.json
|
||||
```
|
||||
|
||||
预期 `200`。
|
||||
|
||||
### 6. 短信服务
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:11190/healthz
|
||||
```
|
||||
|
||||
预期 `200`。
|
||||
|
||||
### 7. 浏览器登录
|
||||
|
||||
访问 `http://<宿主机IP>:8009`:
|
||||
|
||||
| 项 | 值 |
|
||||
| --- | --- |
|
||||
| 账号 | `admin` |
|
||||
| 口令 | `.env` 中的 `MYSQL_ADMIN_PASSWORD` |
|
||||
|
||||
能进入首页工作台、左侧菜单完整显示即部署成功。
|
||||
|
||||
## 首次登录后必做
|
||||
|
||||
1. **修改 admin 口令**:右上角个人中心 → 安全设置
|
||||
2. **处理其余种子账号**:`user02`、`2admin` 等 17 个账号默认与 admin 同一口令,不使用请停用或删除
|
||||
3. **确认字典与菜单加载**:进入「卡信息」页,确认筛选下拉选项不为空
|
||||
|
||||
## 后端自检
|
||||
|
||||
```bash
|
||||
# admin-svr 是否连上数据库
|
||||
docker compose logs admin-svr | grep -iE 'error|panic' | tail -20
|
||||
|
||||
# worker-svr 是否连上 RabbitMQ
|
||||
docker compose logs worker-svr | tail -30
|
||||
|
||||
# upstream-chan 是否连上 Redis 与 Consul
|
||||
docker compose logs upstream-chan | tail -30
|
||||
```
|
||||
|
||||
正常日志不应出现 `connection refused`、`access denied`、`panic`。
|
||||
|
||||
## 停止与重启
|
||||
|
||||
```bash
|
||||
docker compose stop # 停止,保留数据
|
||||
docker compose restart # 重启全部
|
||||
docker compose restart admin-svr # 重启单个
|
||||
docker compose down # 停止并删除容器(./data 不受影响)
|
||||
```
|
||||
|
||||
::: danger 不要执行 down -v
|
||||
`-v` 会删除命名数据卷。本编排数据都在 `./data` 宿主机目录,但 `-v` 仍可能影响同项目的其他卷。停止服务用 `down` 即可。
|
||||
:::
|
||||
|
||||
## 下一步
|
||||
|
||||
- 需要运营商挡板联调 → [可选服务](optional)
|
||||
- 需要域名访问 → [可选服务](optional) 的反向代理章节
|
||||
- 遇到报错 → [故障排查](troubleshooting)
|
||||
@@ -1,125 +0,0 @@
|
||||
---
|
||||
title: 本地开发与预览
|
||||
description: 改文档时用 dev 模式,热更新实时看效果
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 本地开发与预览
|
||||
|
||||
适用:修改文档内容、调整配置、本地看效果。
|
||||
|
||||
## 启动开发服务器
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run dev
|
||||
```
|
||||
|
||||
访问 **`http://127.0.0.1:5173/docs/`**
|
||||
|
||||
::: warning 地址必须带 /docs/
|
||||
`base: '/docs/'` 决定了 dev 服务器入口也在子路径下,直接访问 `http://127.0.0.1:5173/` 会 404。
|
||||
:::
|
||||
|
||||
改动 `docs/` 下任意 `.md` 文件会**热更新**,无需重启。按 `Ctrl+C` 退出。
|
||||
|
||||
## 局域网访问
|
||||
|
||||
需要让同网段其他机器访问时,把监听地址改为 `0.0.0.0`:
|
||||
|
||||
```bash
|
||||
npx vitepress dev . --host 0.0.0.0
|
||||
# 然后访问 http://<本机IP>:5173/docs/
|
||||
```
|
||||
|
||||
## 端口冲突
|
||||
|
||||
默认 5173,被占用时换端口:
|
||||
|
||||
```bash
|
||||
npx vitepress dev . --port 5174
|
||||
```
|
||||
|
||||
## 预览构建产物
|
||||
|
||||
`dev` 模式是源码实时编译,与最终产物有细微差异(如资源路径、代码分割)。发布前建议跑一次真实构建:
|
||||
|
||||
```bash
|
||||
npm run build # 产物在 .vitepress/dist
|
||||
npm run preview # http://127.0.0.1:4173/docs/
|
||||
```
|
||||
|
||||
`preview` 只是本地起静态服务验证产物,**不要用于生产**。
|
||||
|
||||
## 目录结构
|
||||
|
||||
新增页面时按此结构放置:
|
||||
|
||||
```text
|
||||
iotlcm-docs/
|
||||
├── docs/ # 文档源(VitePress srcDir)
|
||||
│ ├── index.md # 站点首页
|
||||
│ ├── manual/ # 使用手册
|
||||
│ │ ├── web/ # Web 管理端
|
||||
│ │ └── mini-program/ # 小程序端
|
||||
│ ├── openapi/ # OpenAPI 合同
|
||||
│ ├── deploy/ # 部署手册(本手册)
|
||||
│ └── public/ # 静态资源,映射到站点根路径
|
||||
│ └── images/ # 文档配图
|
||||
└── .vitepress/
|
||||
├── config.mts # 站点配置(导航、侧边栏)
|
||||
└── theme/ # 主题定制
|
||||
```
|
||||
|
||||
## 新增页面的步骤
|
||||
|
||||
1. 在 `docs/` 下对应目录新建 `xxx.md`,写 frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: 页面标题
|
||||
description: 页面描述
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 页面标题
|
||||
```
|
||||
|
||||
2. 在 `.vitepress/config.mts` 的 `sidebar` 中注册,否则左侧目录不会显示:
|
||||
|
||||
```ts
|
||||
sidebar: {
|
||||
'/deploy/': [
|
||||
{
|
||||
text: '部署手册',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: '概览', link: '/deploy/' },
|
||||
{ text: '新页面', link: '/deploy/xxx' },
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
3. 图片放 `docs/public/images/`,正文用绝对路径引用(public 目录映射到站点根):
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
## 构建产物结构
|
||||
|
||||
```text
|
||||
.vitepress/dist/
|
||||
├── index.html # 首页
|
||||
├── 404.html # 404 页
|
||||
├── favicon.png
|
||||
├── assets/ # JS / CSS / 字体(带 hash)
|
||||
├── images/ # 来自 docs/public/images
|
||||
├── manual/ # 各章节
|
||||
├── openapi/
|
||||
└── deploy/
|
||||
```
|
||||
|
||||
注意产物是**平铺**的,没有 `docs/` 子目录——这正是部署时需要处理路径前缀的原因,见 [路径前缀必读](./path-prefix)。
|
||||
@@ -1,98 +0,0 @@
|
||||
---
|
||||
title: 内网与离线部署
|
||||
description: 无法访问公网 npm 时的构建与部署方案
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 内网与离线部署
|
||||
|
||||
适用:目标机器无法访问公网 npm 源或 Docker Hub。
|
||||
|
||||
## 方案 A:使用内网镜像源
|
||||
|
||||
机器能访问内网 npm 仓库时,直接指定 registry:
|
||||
|
||||
```bash
|
||||
npm ci --registry=https://your-internal-registry/
|
||||
```
|
||||
|
||||
长期使用可写入 `~/.npmrc`:
|
||||
|
||||
```bash
|
||||
npm config set registry https://your-internal-registry/
|
||||
```
|
||||
|
||||
## 方案 B:有网机器构建,只拷产物(最简单)
|
||||
|
||||
产物是纯静态文件,**目标机器不需要装 Node**:
|
||||
|
||||
```bash
|
||||
# 有网机器
|
||||
npm ci && npm run build
|
||||
tar czf iotlcm-docs-dist.tar.gz -C .vitepress/dist .
|
||||
|
||||
# 目标机器
|
||||
tar xzf iotlcm-docs-dist.tar.gz -C /var/www/iotlcm/www/docs
|
||||
```
|
||||
|
||||
注意 `docs` 子目录与 `base: '/docs/'` 的对应关系,详见 [静态托管部署](./static-hosting)。
|
||||
|
||||
## 方案 C:内网构建镜像
|
||||
|
||||
`Dockerfile` 里基础镜像来自 Docker Hub(`node:22-alpine`、`nginx:1.27-alpine`),内网环境需先同步到内部镜像仓库,然后修改 `Dockerfile` 的 `FROM`:
|
||||
|
||||
```dockerfile
|
||||
FROM your-registry/library/node:22-alpine AS builder
|
||||
# ... 中间不变
|
||||
FROM your-registry/library/nginx:1.27-alpine
|
||||
```
|
||||
|
||||
同步镜像:
|
||||
|
||||
```bash
|
||||
# 在有外网的机器上
|
||||
docker pull node:22-alpine
|
||||
docker tag node:22-alpine your-registry/library/node:22-alpine
|
||||
docker push your-registry/library/node:22-alpine
|
||||
|
||||
docker pull nginx:1.27-alpine
|
||||
docker tag nginx:1.27-alpine your-registry/library/nginx:1.27-alpine
|
||||
docker push your-registry/library/nginx:1.27-alpine
|
||||
```
|
||||
|
||||
::: tip 已内置国内 apk 源
|
||||
`Dockerfile` 的 builder 阶段已把 Alpine 源换成 `mirrors.aliyun.com`,无需额外处理。
|
||||
:::
|
||||
|
||||
## 方案 D:离线安装依赖
|
||||
|
||||
既不能访问公网 npm、也没有内网 registry 时,可在有网机器打包 `node_modules`:
|
||||
|
||||
```bash
|
||||
# 有网机器(与目标机器同架构、同 Node 大版本)
|
||||
npm ci
|
||||
tar czf iotlcm-docs-full.tar.gz \
|
||||
--exclude=.git --exclude=.vitepress/cache --exclude=.vitepress/dist .
|
||||
|
||||
# 目标机器
|
||||
tar xzf iotlcm-docs-full.tar.gz -C /opt/iotlcm-docs
|
||||
cd /opt/iotlcm-docs
|
||||
npm run build
|
||||
```
|
||||
|
||||
::: warning 架构与版本必须一致
|
||||
`node_modules` 里可能含平台相关的二进制依赖,跨架构(如 x86 → ARM)或跨 Node 大版本会失败。跨架构场景请用方案 B 直接拷产物。
|
||||
:::
|
||||
|
||||
## 方案对比
|
||||
|
||||
| 方案 | 需要外网 | 目标机器需 Node | 适用场景 |
|
||||
| --- | --- | --- | --- |
|
||||
| A 内网 registry | 否(有内网源) | 是 | 有内网 npm 仓库 |
|
||||
| B 只拷产物 | 仅在构建机 | **否** | 最通用,推荐 |
|
||||
| C 内网镜像 | 仅在同步镜像时 | 否(用 Docker) | 容器化生产环境 |
|
||||
| D 打包 node_modules | 仅在构建机 | 是 | 需在目标机器改文档后构建 |
|
||||
|
||||
::: tip 推荐优先级
|
||||
优先选 **B(只拷产物)**——最简单,目标机器零依赖。需要容器化则选 **C**。
|
||||
:::
|
||||
@@ -0,0 +1,184 @@
|
||||
---
|
||||
title: 升级与备份
|
||||
description: 版本固定、升级回滚、数据备份与日志清理
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 升级与备份
|
||||
|
||||
## 版本固定
|
||||
|
||||
生产环境**必须固定镜像标签**,不要用 `latest`:
|
||||
|
||||
- `latest` 会随仓库推送变化,同一份编排在不同时间拉到的镜像不同
|
||||
- 出问题无法确定运行版本
|
||||
- 回滚时没有明确目标
|
||||
|
||||
`.env` 中指定:
|
||||
|
||||
```bash
|
||||
TAG=1.0.3
|
||||
```
|
||||
|
||||
记录当前部署版本:
|
||||
|
||||
```bash
|
||||
echo "$(date '+%Y-%m-%d %H:%M') TAG=$(grep '^TAG=' .env | cut -d= -f2)" >> deploy-history.log
|
||||
```
|
||||
|
||||
## 升级
|
||||
|
||||
```bash
|
||||
# 1. 备份(不要跳过,见下节)
|
||||
./backup.sh
|
||||
|
||||
# 2. 拉新镜像
|
||||
vi .env # 修改 TAG
|
||||
docker compose pull
|
||||
|
||||
# 3. 更新
|
||||
docker compose up -d
|
||||
|
||||
# 4. 验证
|
||||
docker compose ps
|
||||
docker compose logs --tail=50 admin-svr
|
||||
```
|
||||
|
||||
滚动更新单个服务减少中断:
|
||||
|
||||
```bash
|
||||
docker compose up -d --no-deps admin-svr
|
||||
```
|
||||
|
||||
## 回滚
|
||||
|
||||
```bash
|
||||
vi .env # TAG 改回旧版本
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
::: warning 数据库变更不能随代码回滚
|
||||
若新版本含数据库结构变更,回滚代码后数据库仍是新结构。升级前确认变更可逆,或保留升级前的完整备份。
|
||||
:::
|
||||
|
||||
## 数据库重建
|
||||
|
||||
`schema/` 脚本只在 `data/mysql` 为空时执行:
|
||||
|
||||
| 情况 | 是否需要重建 |
|
||||
| --- | --- |
|
||||
| 升级应用镜像 | 否 |
|
||||
| 修改 `config/` | 否,重启对应服务即可 |
|
||||
| 修改 `schema/` 脚本 | 是(会丢数据) |
|
||||
| 首次初始化失败 | 是 |
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
sudo rm -rf ./data/mysql
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
::: danger 生产环境禁止清空 data/mysql
|
||||
生产数据必须通过数据库工具单独备份与迁移,不能依赖 schema 重建。
|
||||
:::
|
||||
|
||||
## 备份
|
||||
|
||||
### 备份脚本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
STAMP=$(date '+%Y%m%d-%H%M%S')
|
||||
DEST="./backups/${STAMP}"
|
||||
mkdir -p "$DEST"
|
||||
|
||||
echo "[1/3] 导出数据库..."
|
||||
docker compose exec -T mysql sh -c \
|
||||
'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --routines --triggers --events db_iotlcm' \
|
||||
| gzip > "${DEST}/db_iotlcm.sql.gz"
|
||||
|
||||
echo "[2/3] 备份配置..."
|
||||
tar czf "${DEST}/config.tar.gz" config/ .env
|
||||
|
||||
echo "[3/3] 备份上传文件..."
|
||||
tar czf "${DEST}/upload.tar.gz" data/upload/ 2>/dev/null || echo " 上传目录为空,跳过"
|
||||
|
||||
echo "完成:${DEST}"
|
||||
ls -lh "$DEST"
|
||||
```
|
||||
|
||||
```bash
|
||||
chmod +x backup.sh && ./backup.sh
|
||||
```
|
||||
|
||||
### 只备份数据库
|
||||
|
||||
```bash
|
||||
docker compose exec -T mysql sh -c \
|
||||
'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction db_iotlcm' \
|
||||
| gzip > "db_iotlcm-$(date +%Y%m%d).sql.gz"
|
||||
```
|
||||
|
||||
### 恢复
|
||||
|
||||
```bash
|
||||
gunzip -c db_iotlcm-20260101.sql.gz | \
|
||||
docker compose exec -T mysql sh -c \
|
||||
'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm'
|
||||
```
|
||||
|
||||
### 备份内容与优先级
|
||||
|
||||
| 内容 | 位置 | 重要性 |
|
||||
| --- | --- | --- |
|
||||
| 数据库 | MySQL 容器内 `db_iotlcm` | **最高**,业务数据全部在此 |
|
||||
| 配置 | `config/` + `.env` | 高,丢失需重新配置 |
|
||||
| 上传文件 | `data/upload/` | 中,附件与图片 |
|
||||
| Redis 数据 | `data/redis/` | 低,缓存可重建 |
|
||||
| RabbitMQ 队列 | `data/rabbitmq/` | 低,未消费消息会丢失 |
|
||||
| 日志 | `data/logs/` | 低,排障用 |
|
||||
|
||||
### 备份策略
|
||||
|
||||
| 场景 | 频率 | 保留 |
|
||||
| --- | --- | --- |
|
||||
| 生产 | 每日一次 + 升级前 | 30 天 |
|
||||
| 试用 | 有数据变更时 | 7 天 |
|
||||
|
||||
备份文件应转存到异机或对象存储,不要与数据库同盘。
|
||||
|
||||
## 日志清理
|
||||
|
||||
日志写在 `data/logs/` 下按服务分目录,应用侧已按小时轮转(`{Y-m-d-H}.log`)。清理旧日志:
|
||||
|
||||
```bash
|
||||
# 删除 7 天前的日志
|
||||
find ./data/logs -name '*.log' -mtime +7 -delete
|
||||
```
|
||||
|
||||
建议配置 cron 定期执行:
|
||||
|
||||
```cron
|
||||
0 3 * * * root find /path/to/iotlcm-deploy/data/logs -name '*.log' -mtime +7 -delete
|
||||
```
|
||||
|
||||
## 监控
|
||||
|
||||
基础组件自带端点:
|
||||
|
||||
| 服务 | 端点 |
|
||||
| --- | --- |
|
||||
| RabbitMQ | `http://<host>:15672` 管理界面 |
|
||||
| Consul | `http://<host>:8500/ui` |
|
||||
| MySQL | `docker compose exec mysql mysqladmin status` |
|
||||
|
||||
建议告警指标:
|
||||
|
||||
- MySQL 连接数与磁盘使用率
|
||||
- Redis 内存使用率
|
||||
- RabbitMQ 队列积压(`iotlcm.batch.execute`、`iotlcm.app.business`)
|
||||
- worker-svr 全量同步任务失败数
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
title: 可选服务
|
||||
description: 运营商接口挡板、飞书通知与反向代理
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 可选服务
|
||||
|
||||
以下服务默认不启动,按需通过 profile 开启。
|
||||
|
||||
## 运营商接口挡板
|
||||
|
||||
### 用途
|
||||
|
||||
模拟中国移动 OneLink 接口。**没有真实运营商环境时,靠它跑通完整链路**:
|
||||
|
||||
```text
|
||||
admin-svr → worker-svr → upstream-chan → onelink-mock
|
||||
```
|
||||
|
||||
不开挡板时,`upstream-chan` 会尝试连接真实运营商接口。没有凭据则批次执行、状态同步、流量查询全部失败。
|
||||
|
||||
### 启动
|
||||
|
||||
```bash
|
||||
docker compose --profile with-mock up -d
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/api.json
|
||||
```
|
||||
|
||||
预期 `200`。接口文档在 `http://127.0.0.1:8080/swagger`。
|
||||
|
||||
### 配置
|
||||
|
||||
挡板自身无需配置,数据存在容器内 SQLite(`./data/mock/onelink_mock.db`)。
|
||||
|
||||
`upstream-chan` 配置中已预置指向:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
sim_yd_mock_v5:
|
||||
url: "http://onelink-mock:8080/v5/"
|
||||
```
|
||||
|
||||
数据库种子脚本 `schema/08-seed-mock-channel.sql` 已插入 Mock 通道(`channel_yd_mock`)并指向该地址。
|
||||
|
||||
`app-server` 若使用挡板,把 `upstream.onelinkMock.enabled` 改为 `true`,`baseUrl` 填 `http://onelink-mock:8080/v5`。
|
||||
|
||||
### 造测试卡
|
||||
|
||||
挡板支持生成模拟卡片,用于验证查询、开停复机、续费流程。接口见挡板 Swagger 页面。
|
||||
|
||||
### 切换到真实运营商
|
||||
|
||||
1. 删除或注释 `upstream-chan` 配置中的 `sim_yd_mock_v5` 段
|
||||
2. 在平台「通道管理」录入真实运营商通道(AppKey、AppSecret、接口地址)
|
||||
3. 停用 Mock 通道,避免卡片误挂
|
||||
|
||||
```bash
|
||||
docker compose --profile with-mock stop onelink-mock
|
||||
```
|
||||
|
||||
## 飞书通知
|
||||
|
||||
### 用途
|
||||
|
||||
把全量同步完成、失败告警等运维事件推送到飞书群。
|
||||
|
||||
### 准备
|
||||
|
||||
在飞书开放平台创建自建应用,获取 `appId`、`appSecret` 与目标群的 `chatId`,填入 `.env`:
|
||||
|
||||
```bash
|
||||
FEISHU_APP_ID=cli_xxxxxxxx
|
||||
FEISHU_APP_SECRET=xxxxxxxx
|
||||
FEISHU_CHAT_ID=oc_xxxxxxxx
|
||||
```
|
||||
|
||||
### 启动
|
||||
|
||||
```bash
|
||||
docker compose --profile with-bot up -d
|
||||
```
|
||||
|
||||
bot 读取 `worker-svr` 生成的全量同步归档(`./data/logs/worker-svr/full-sync`),该目录有数据后才会产生通知。
|
||||
|
||||
## 反向代理
|
||||
|
||||
### 用途
|
||||
|
||||
把分散在各端口的服务统一到域名下按路径分流。宿主机已有 nginx 或面板时不需要。
|
||||
|
||||
### 域名与分流
|
||||
|
||||
编辑 `config/nginx/iotlcm.conf`:
|
||||
|
||||
| 域名 | 分流到 |
|
||||
| --- | --- |
|
||||
| `api.your-domain.example.com` | openapi(`/openapi/v3/`、`/route/open/`) |
|
||||
| `admin.your-domain.example.com` | 管理端前端 + `/docs/` 文档站 |
|
||||
| `app.your-domain.example.com` | 小程序前端 + `/app/api/v1/` 后端 |
|
||||
|
||||
### 启动
|
||||
|
||||
```bash
|
||||
docker compose --profile with-nginx up -d
|
||||
```
|
||||
|
||||
默认监听 80,改 `.env` 的 `NGINX_HTTP_PORT` 可调整。
|
||||
|
||||
### 使用宿主机 nginx
|
||||
|
||||
不启动容器时,把 `config/nginx/iotlcm.conf` 中的 `upstream` 与 `location` 段落复制到宿主机 nginx 配置。
|
||||
|
||||
::: warning 宿主机 nginx 无法解析容器名
|
||||
需要把 `upstream` 里的服务名换成宿主机地址:
|
||||
|
||||
```nginx
|
||||
# 容器内写法
|
||||
server admin-svr:8008;
|
||||
# 宿主机写法
|
||||
server 127.0.0.1:8008;
|
||||
```
|
||||
:::
|
||||
|
||||
### 启用 HTTPS
|
||||
|
||||
自带配置未包含证书,启用需三步:
|
||||
|
||||
1. 准备证书,放入 `data/certs/`
|
||||
2. 在 server 块加入 SSL 配置:
|
||||
|
||||
```nginx
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/nginx/certs/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/certs/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
```
|
||||
|
||||
3. 给 nginx 服务增加证书目录挂载,并加 80 → 443 跳转:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./data/certs:/etc/nginx/certs:ro
|
||||
```
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name admin.your-domain.example.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
## 组合启动
|
||||
|
||||
多个 profile 可同时启用:
|
||||
|
||||
```bash
|
||||
docker compose --profile with-mock --profile with-nginx up -d
|
||||
```
|
||||
|
||||
查看指定 profile 下的服务清单:
|
||||
|
||||
```bash
|
||||
docker compose --profile with-mock config --services
|
||||
```
|
||||
@@ -1,96 +0,0 @@
|
||||
---
|
||||
title: 路径前缀必读
|
||||
description: base=/docs/ 导致的部署白屏问题与三种正确做法
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 路径前缀必读
|
||||
|
||||
这是本项目最容易出错的地方,**部署失败九成是这个原因**。部署前请务必读完本页。
|
||||
|
||||
## 问题成因
|
||||
|
||||
文档站配置里 `base: '/docs/'`(见 `.vitepress/config.mts`),因此构建产物中所有资源引用都是**绝对路径**:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/docs/assets/style.xxx.css">
|
||||
<script src="/docs/assets/app.xxx.js"></script>
|
||||
```
|
||||
|
||||
而产物目录 `.vitepress/dist` 是**平铺**的,`index.html`、`assets/` 直接在根,**没有 `docs/` 子目录**。
|
||||
|
||||
由此推出两条铁律:
|
||||
|
||||
1. **对外访问路径必须是 `/docs/`**,且服务器必须把 `/docs/` 前缀剥掉后再落到产物目录
|
||||
2. 若想挂到别的前缀(如 `/manual/`),必须**先改 `base` 再重新构建**,不能只改服务器配置
|
||||
|
||||
::: warning 典型故障:白屏
|
||||
如果配置成「从根路径直接服务产物」(`root` 指向 dist,访问 `/`),页面 HTML 会返回 200,但 `/docs/assets/*.js` 全部 404 → **浏览器白屏,没有任何报错提示**。
|
||||
|
||||
这种情况最坑的地方是:`curl /` 返回 200,看起来部署成功了,实际打开浏览器是空白页。
|
||||
:::
|
||||
|
||||
## 三种正确做法
|
||||
|
||||
| 做法 | 适用 | 关键配置 |
|
||||
| --- | --- | --- |
|
||||
| nginx `alias` 剥前缀 | 容器部署(推荐) | `location /docs/ { alias /usr/share/nginx/html/; }` |
|
||||
| 把产物放进 `docs/` 子目录 | 静态托管 / 对象存储 | `dist/` → `www/docs/` |
|
||||
| 改 `base: '/'` 重新构建 | 独占域名根路径 | 改配置后必须重新 `npm run build` |
|
||||
|
||||
### 做法一:nginx alias 剥前缀
|
||||
|
||||
```nginx
|
||||
location /docs/ {
|
||||
alias /usr/share/nginx/html/;
|
||||
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||
}
|
||||
```
|
||||
|
||||
`alias` 会剥掉 `location` 的前缀,`/docs/assets/app.js` 实际读到 `/usr/share/nginx/html/assets/app.js`,正好对上平铺的产物目录。
|
||||
|
||||
详见 [推荐环境部署:Docker + nginx](./docker-nginx)。
|
||||
|
||||
### 做法二:产物放进 docs 子目录
|
||||
|
||||
```bash
|
||||
sudo cp -r .vitepress/dist /var/www/iotlcm/www/docs
|
||||
```
|
||||
|
||||
```nginx
|
||||
root /var/www/iotlcm/www;
|
||||
location /docs/ {
|
||||
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||
}
|
||||
```
|
||||
|
||||
`/docs/assets/app.js` 自然映射到 `/var/www/iotlcm/www/docs/assets/app.js`,不需要 `alias`。
|
||||
|
||||
详见 [静态托管部署](./static-hosting)。
|
||||
|
||||
### 做法三:改 base 重新构建
|
||||
|
||||
```bash
|
||||
sed -i "s#base: '/docs/'#base: '/'#" .vitepress/config.mts
|
||||
npm run build
|
||||
```
|
||||
|
||||
仅适用于文档站独占域名根路径的场景。
|
||||
|
||||
::: danger 不要只改服务器配置
|
||||
`base` 与访问前缀必须一致。改了 nginx 却不改 `base`(或反之),一定会白屏。
|
||||
:::
|
||||
|
||||
## cleanUrls 的额外要求
|
||||
|
||||
配置启用了 `cleanUrls: true`,页面 URL 不带 `.html` 后缀(如 `/manual/web/` 而非 `/manual/web.html`)。
|
||||
|
||||
因此服务器的 `try_files` **必须**包含 `$uri.html` 与 `$uri/index.html` 映射:
|
||||
|
||||
```nginx
|
||||
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||
```
|
||||
|
||||
缺了这两项的表现是:首页正常,但点进任何内页都是 404。
|
||||
|
||||
对象存储场景下需确认支持「无后缀映射到 `.html`」,否则要关闭 `cleanUrls` 重新构建。
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
title: 环境准备
|
||||
description: 部署 IotLCM 所需的硬件、系统、Docker 与网络条件
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 环境准备
|
||||
|
||||
## 硬件要求
|
||||
|
||||
按数据规模选择:
|
||||
|
||||
| 场景 | CPU | 内存 | 磁盘 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 试用 / 演示 | 2 核 | 4 GB | 40 GB | 少量卡片,建议停用定时同步任务 |
|
||||
| 生产(中小规模) | 4 核 | 8 GB | 200 GB | 万级卡片,开启全量同步 |
|
||||
| 生产(大规模) | 8 核+ | 16 GB+ | 500 GB+ | 十万级卡片,建议数据库独立部署 |
|
||||
|
||||
磁盘估算:MySQL 数据约 1~2 KB/张卡;日志随同步任务量增长,建议单独挂载并配置轮转。
|
||||
|
||||
生产 8 GB 场景的内存分配参考:
|
||||
|
||||
| 服务 | 占用 |
|
||||
| --- | --- |
|
||||
| MySQL | 1~2 GB |
|
||||
| Redis | 200~500 MB |
|
||||
| RabbitMQ | 300~500 MB |
|
||||
| worker-svr | 500 MB~1 GB(与并发同步卡片数相关) |
|
||||
| 其余业务服务 | 各 100~200 MB |
|
||||
|
||||
## 系统要求
|
||||
|
||||
| 项 | 要求 |
|
||||
| --- | --- |
|
||||
| 操作系统 | Linux x86_64(CentOS 7+ / Ubuntu 20.04+ / Debian 11+) |
|
||||
| 内核 | 3.10 以上 |
|
||||
| 文件系统 | ext4 / xfs |
|
||||
| 时区 | 建议 `Asia/Shanghai`,与 `.env` 的 `TZ` 保持一致 |
|
||||
|
||||
## Docker 要求
|
||||
|
||||
| 组件 | 最低版本 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| Docker Engine | 20.10 | 需支持 `healthcheck` 与 `profiles` |
|
||||
| Docker Compose | v2.0(插件版) | 使用 `docker compose` 而非 `docker-compose` |
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
docker version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
`docker compose version` 需 ≥ v2.0。若只有 `docker-compose`(v1),请升级到 Compose V2 插件。
|
||||
|
||||
## 镜像仓库
|
||||
|
||||
平台镜像由官方镜像仓库分发,需要:
|
||||
|
||||
1. 能访问镜像仓库地址(内网仓库需先配置网络或代理)
|
||||
2. 具备拉取权限
|
||||
|
||||
```bash
|
||||
docker login <镜像仓库地址>
|
||||
```
|
||||
|
||||
`.env` 的 `REGISTRY` 填写仓库地址,不含 `https://` 与末尾斜杠:
|
||||
|
||||
```bash
|
||||
REGISTRY=registry.example.com
|
||||
```
|
||||
|
||||
基础组件从 `<REGISTRY>/library/` 拉取(mysql、redis、rabbitmq、consul、nginx),业务镜像从 `<REGISTRY>/iotlcm/` 拉取。
|
||||
|
||||
## 网络与防火墙
|
||||
|
||||
仅需对外开放反向代理端口(默认 80 / 443),其余服务端口应限制为内网访问。
|
||||
|
||||
```bash
|
||||
# 示例:firewalld 仅放行 80/443
|
||||
firewall-cmd --permanent --add-service=http
|
||||
firewall-cmd --permanent --add-service=https
|
||||
firewall-cmd --reload
|
||||
```
|
||||
|
||||
::: tip 默认只监听本机
|
||||
`admin-svr`、`admin-ui`、`openapi`、`app-server`、`app-web`、`docs` 默认绑定 `127.0.0.1`,只能本机访问。对外需通过反向代理,见[可选服务](optional)。
|
||||
:::
|
||||
|
||||
## 外部依赖(可选)
|
||||
|
||||
| 服务 | 用途 | 不配置的影响 |
|
||||
| --- | --- | --- |
|
||||
| TDengine 3.x | 存流量历史数据 | 历史流量查询不可用,主流程正常 |
|
||||
| 短信服务商 | 发送验证码 | 无法发送短信 |
|
||||
| 微信支付 | 在线支付 | 支付功能不可用 |
|
||||
| 飞书 | 运维通知 | 不启动 bot 即可 |
|
||||
|
||||
TDengine 需开启 WebSocket 接口(默认 6041),在 `.env` 中配置:
|
||||
|
||||
```bash
|
||||
TDENGINE_HOST=tdengine.example.com
|
||||
TDENGINE_PORT=6041
|
||||
TDENGINE_USER=root
|
||||
TDENGINE_PASSWORD=
|
||||
TDENGINE_DAILY_DB=iotlcm_daily
|
||||
TDENGINE_MONTHLY_DB=iotlcm_monthly
|
||||
```
|
||||
|
||||
不使用时全部留空。
|
||||
@@ -1,159 +0,0 @@
|
||||
---
|
||||
title: 静态托管部署
|
||||
description: 不使用 Docker 时,用 nginx / 对象存储 / CDN 托管静态产物
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 静态托管部署
|
||||
|
||||
适用:已有 nginx / Apache / Caddy,或使用对象存储 / CDN,不想引入 Docker。
|
||||
|
||||
核心流程只有两步:**构建产物** → **托管静态文件**。
|
||||
|
||||
## 编译产物
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run build
|
||||
# 产物在 .vitepress/dist
|
||||
```
|
||||
|
||||
本地验证构建结果(不要用于生产):
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
# http://127.0.0.1:4173/docs/
|
||||
```
|
||||
|
||||
## nginx:子路径挂载(推荐)
|
||||
|
||||
把产物放到一个 `docs` 子目录,即可与 `base` 天然对齐,**无需 alias**:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/iotlcm/www
|
||||
sudo cp -r .vitepress/dist /var/www/iotlcm/www/docs
|
||||
```
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /var/www/iotlcm/www;
|
||||
|
||||
location /docs/ {
|
||||
try_files $uri $uri.html $uri/ $uri/index.html =404;
|
||||
}
|
||||
|
||||
location = /docs {
|
||||
return 301 /docs/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
此处 `root` 指向父目录、产物在 `docs/` 子目录下,`/docs/assets/app.js` 会自然映射到 `/var/www/iotlcm/www/docs/assets/app.js`,不需要 `alias`。
|
||||
|
||||
## nginx:根路径独占域名
|
||||
|
||||
若文档站独占一个域名、需要挂在 `/`,必须**改 `base` 后重新构建**:
|
||||
|
||||
```bash
|
||||
sed -i "s#base: '/docs/'#base: '/'#" .vitepress/config.mts
|
||||
npm run build
|
||||
```
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name docs.example.com;
|
||||
root /var/www/iotlcm-docs;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri.html $uri/ =404;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: danger 不要只改服务器配置
|
||||
`base` 与访问前缀必须一致。改了 nginx 却不改 `base`(或反之),一定会白屏。详见 [路径前缀必读](./path-prefix)。
|
||||
:::
|
||||
|
||||
## Apache
|
||||
|
||||
需要启用 `mod_rewrite`,用 `.htaccess` 做 cleanUrls 映射:
|
||||
|
||||
```apache
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /docs/
|
||||
|
||||
# 已存在的文件/目录直接返回
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# 无后缀路径映射到 .html 或 index.html
|
||||
RewriteCond %{REQUEST_FILENAME}.html -f
|
||||
RewriteRule ^(.+)$ $1.html [L]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME}/index.html -f
|
||||
RewriteRule ^(.+)$ $1/index.html [L]
|
||||
</IfModule>
|
||||
```
|
||||
|
||||
产物目录放置方式与 nginx 子路径方案一致:`/var/www/html/docs/`。
|
||||
|
||||
## Caddy
|
||||
|
||||
Caddy 配置更简洁,`try_files` 语义与 nginx 类似:
|
||||
|
||||
```caddyfile
|
||||
example.com {
|
||||
handle_path /docs/* {
|
||||
root * /var/www/iotlcm-docs
|
||||
try_files {path} {path}.html {path}/index.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
或产物放在子目录、不做前缀处理:
|
||||
|
||||
```caddyfile
|
||||
example.com {
|
||||
root * /var/www/iotlcm/www
|
||||
try_files {path} {path}.html {path}/index.html
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
## 对象存储 / CDN
|
||||
|
||||
产物是纯静态文件,可直接上传到 S3 / OSS / MinIO 等:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
# 上传 .vitepress/dist 全部内容到桶内 docs/ 前缀下
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- 上传时保持 `/docs/` 前缀(或改 `base` 后重新构建)
|
||||
- 配置**默认首页**为 `docs/index.html`
|
||||
- 配置 **404 页面**为 `docs/404.html`(产物已自带)
|
||||
- `cleanUrls: true` 意味着页面 URL 不带 `.html`,需对象存储支持「无后缀映射到 .html」,否则需关闭 `cleanUrls` 重新构建
|
||||
|
||||
::: tip 关闭 cleanUrls
|
||||
若对象存储不支持无后缀映射,编辑 `.vitepress/config.mts` 把 `cleanUrls: true` 改为 `false`,重新构建后页面 URL 会带 `.html` 后缀。
|
||||
:::
|
||||
|
||||
## pnpm / yarn
|
||||
|
||||
仓库同时提供 `package-lock.json` 与 `pnpm-lock.yaml`。使用 pnpm:
|
||||
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
npm 与 pnpm 二选一即可,**不要混用**(会产生不一致的 `node_modules`)。
|
||||
@@ -0,0 +1,243 @@
|
||||
---
|
||||
title: 故障排查
|
||||
description: 部署与运行中的常见问题及处理
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 故障排查
|
||||
|
||||
## 通用排查
|
||||
|
||||
```bash
|
||||
docker compose ps # 容器状态
|
||||
docker compose logs --tail=100 <服务> # 最近日志
|
||||
docker compose logs -f <服务> # 跟随日志
|
||||
docker compose exec <服务> sh # 进容器
|
||||
```
|
||||
|
||||
## 启动类
|
||||
|
||||
### MySQL 一直不 healthy
|
||||
|
||||
**现象**:`iotlcm-mysql` 反复重启或一直 `starting`,其余服务不启动。
|
||||
|
||||
```bash
|
||||
docker compose logs mysql | tail -50
|
||||
```
|
||||
|
||||
| 日志关键词 | 原因 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| `Access denied` | root 口令与旧数据卷不一致 | 改 `.env` 与卷内一致,或清空 `data/mysql` 重建 |
|
||||
| `Can't create/write to file` | 目录权限 | `sudo chown -R 999:999 data/mysql` |
|
||||
| `No space left on device` | 磁盘满 | 清理磁盘 |
|
||||
| `Can't open shared library` | 架构不匹配 | 确认镜像与宿主机架构一致 |
|
||||
|
||||
### schema 初始化失败
|
||||
|
||||
```bash
|
||||
docker compose logs mysql | grep -iE 'error' | head -30
|
||||
```
|
||||
|
||||
| 原因 | 处理 |
|
||||
| --- | --- |
|
||||
| `MYSQL_APP_PASSWORD` 未设置,`04-users.sh` 退出 | 补上后清空 `data/mysql` 重建 |
|
||||
| `MYSQL_ADMIN_PASSWORD` 未设置,`05-reset-passwords.sh` 退出 | 同上 |
|
||||
| SQL 脚本语法问题 | 单独执行该脚本定位 |
|
||||
|
||||
::: warning 改脚本后必须清空数据卷
|
||||
MySQL 只在 `data/mysql` 为空时执行 `schema/`,文件改了不会自动重跑:
|
||||
|
||||
```bash
|
||||
docker compose down && sudo rm -rf ./data/mysql && docker compose up -d
|
||||
```
|
||||
:::
|
||||
|
||||
### 业务服务反复重启
|
||||
|
||||
```bash
|
||||
docker compose logs --tail=80 admin-svr
|
||||
```
|
||||
|
||||
| 报错 | 原因 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| `possible config files "config" not found` | `config/` 不存在或挂载失败 | `cp -r config.example config`,确认 `config/admin-svr/config.yaml` 存在 |
|
||||
| `dial tcp mysql:3306: connection refused` | MySQL 未就绪 | 等 MySQL healthy |
|
||||
| `Access denied for user 'u_iotlcm'` | 应用账号口令与库内不一致 | 确认 `.env` 的 `MYSQL_APP_PASSWORD` 与初始化时一致 |
|
||||
| `panic: unsupported provider xxx` | 通道模板与镜像版本不匹配 | 确认镜像版本与 schema 版本配套 |
|
||||
|
||||
## 功能类
|
||||
|
||||
### 登录后提示未授权 / 接口 401
|
||||
|
||||
**原因**:`JWT_SIGNING_KEY` 在服务间不一致。
|
||||
|
||||
确认 `admin-svr`、`openapi`、`app-server` 三份配置的 `jwt.signingKey` 都引用 `${JWT_SIGNING_KEY}` 且 `.env` 有值,然后重启:
|
||||
|
||||
```bash
|
||||
docker compose restart admin-svr openapi app-server
|
||||
```
|
||||
|
||||
### 登录成功但页面空白 / 菜单不显示
|
||||
|
||||
1. 浏览器 F12 看 Network 有无 4xx/5xx
|
||||
2. 检查菜单数据:
|
||||
|
||||
```bash
|
||||
docker compose exec mysql sh -c \
|
||||
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e "SELECT COUNT(*) FROM sys_menu WHERE status=0;"'
|
||||
```
|
||||
|
||||
菜单为 0 说明种子数据未导入,需重建库。
|
||||
|
||||
3. 检查角色菜单关联:
|
||||
|
||||
```bash
|
||||
docker compose exec mysql sh -c \
|
||||
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e "SELECT COUNT(*) FROM sys_role_menu;"'
|
||||
```
|
||||
|
||||
### 卡信息页通道列空白 / 筛选无选项
|
||||
|
||||
**原因**:Mock 通道未插入。
|
||||
|
||||
```bash
|
||||
docker compose exec mysql sh -c \
|
||||
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e "SELECT channel_id, channel_name FROM t_sim_channels;"'
|
||||
```
|
||||
|
||||
为空则手工执行种子脚本:
|
||||
|
||||
```bash
|
||||
docker compose exec -T mysql sh -c \
|
||||
'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm' < schema/08-seed-mock-channel.sql
|
||||
```
|
||||
|
||||
### 批次执行一直失败
|
||||
|
||||
按顺序排查:
|
||||
|
||||
1. **挡板是否启动**(无真实运营商时)
|
||||
|
||||
```bash
|
||||
docker compose ps | grep onelink-mock
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/api.json
|
||||
```
|
||||
|
||||
未启动则 `docker compose --profile with-mock up -d`。
|
||||
|
||||
2. **worker-svr 是否连上 RabbitMQ**
|
||||
|
||||
```bash
|
||||
docker compose logs worker-svr | grep -i 'rabbit\|amqp' | tail -20
|
||||
```
|
||||
|
||||
3. **upstream-chan 是否能连到上游**
|
||||
|
||||
```bash
|
||||
docker compose logs upstream-chan --tail=50
|
||||
```
|
||||
|
||||
4. **是否被运营商限流**
|
||||
|
||||
检查 worker-svr 日志中的 `rate limit`、`429`、`12021`。核对 `batch.inbound.upstreamRateLimit` 的 QPS 是否超过上游配额。
|
||||
|
||||
### 历史流量查询无数据
|
||||
|
||||
**原因**:TDengine 未配置或不可达。
|
||||
|
||||
```bash
|
||||
docker compose logs worker-svr | grep -i 'tdengine\|taos' | tail -20
|
||||
```
|
||||
|
||||
确认 `.env` 的 `TDENGINE_*` 已填,且 WebSocket 端口(默认 6041)可达。不用该功能留空即可。
|
||||
|
||||
### 短信发不出去
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:11190/healthz
|
||||
docker compose logs sms --tail=50
|
||||
```
|
||||
|
||||
| 报错 | 原因 |
|
||||
| --- | --- |
|
||||
| `InvalidAccessKeyId` | `.env` 中 AK 未填或错误 |
|
||||
| `isv.SMS_SIGNATURE_ILLEGAL` | `signName` 与服务商审核签名不一致 |
|
||||
| `isv.SMS_TEMPLATE_ILLEGAL` | 模板 ID 未审核通过 |
|
||||
| `缺少 config.templateId` | 场景模板库未配置,调用方需自带 templateId |
|
||||
|
||||
### 图片上传失败 / 不显示
|
||||
|
||||
```bash
|
||||
ls -la data/upload/
|
||||
sudo chown -R 1000:1000 data/upload
|
||||
docker compose restart admin-svr admin-ui
|
||||
```
|
||||
|
||||
## 性能类
|
||||
|
||||
### CPU 被 worker-svr 打满
|
||||
|
||||
**原因**:定时任务开启全量同步,大量卡片并发查询上游。
|
||||
|
||||
1. 降低并发与限流:
|
||||
|
||||
```yaml
|
||||
batch:
|
||||
inbound:
|
||||
upstreamConcurrency: 50 # 从 300 降低
|
||||
upstreamBatchSize: 1
|
||||
```
|
||||
|
||||
2. 演示 / 试用环境直接停用定时任务:
|
||||
|
||||
```bash
|
||||
docker compose exec mysql sh -c \
|
||||
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e "UPDATE sys_job SET status=1;"'
|
||||
```
|
||||
|
||||
种子数据中定时任务默认已全部停用(`status=1`)。
|
||||
|
||||
### 磁盘持续增长
|
||||
|
||||
```bash
|
||||
du -sh data/* | sort -rh
|
||||
```
|
||||
|
||||
| 目录 | 处理 |
|
||||
| --- | --- |
|
||||
| `data/logs` | 清理旧日志,见[升级与备份](operations) |
|
||||
| `data/upload` | 检查异常大的上传文件 |
|
||||
| `data/mysql` | 数据库增长正常,关注 binlog |
|
||||
| `data/redis` | AOF 文件,正常 |
|
||||
|
||||
## 网络类
|
||||
|
||||
### 容器间无法互访
|
||||
|
||||
```bash
|
||||
docker network inspect iotlcm-network
|
||||
docker compose exec admin-svr sh -c 'nc -zv mysql 3306'
|
||||
```
|
||||
|
||||
配置中应使用 `mysql:3306`、`redis:6379` 这样的服务名,不要写 `127.0.0.1`。
|
||||
|
||||
### 宿主机访问不了 127.0.0.1 绑定的服务
|
||||
|
||||
`admin-svr`、`admin-ui`、`openapi`、`app-server`、`app-web`、`docs` 默认只绑 `127.0.0.1`。对外访问需:
|
||||
|
||||
1. 用 `--profile with-nginx` 启动反向代理,或
|
||||
2. 宿主机 nginx 转发到 `127.0.0.1:<端口>`
|
||||
|
||||
::: danger 不要直接把端口改成 0.0.0.0
|
||||
这会把管理端、数据库等内部服务暴露到公网。
|
||||
:::
|
||||
|
||||
## 求助时提供的信息
|
||||
|
||||
```bash
|
||||
docker compose version
|
||||
docker version --format '{{.Server.Version}}'
|
||||
docker compose ps
|
||||
docker compose logs --tail=200 <服务名>
|
||||
df -h && free -h
|
||||
```
|
||||
Reference in New Issue
Block a user