From 07ddaa996818a522d051bb2689c79a073a5591a1 Mon Sep 17 00:00:00 2001 From: Dai0814 <3490199389@qq.com> Date: Mon, 26 Oct 2026 16:51:33 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=89=8B=E5=86=8C=E9=A1=B5=E5=B9=B6=E4=BF=AE=E5=A4=8D=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E9=83=A8=E7=BD=B2=E7=99=BD=E5=B1=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 docs/deploy/ 部署手册:推荐环境(Node 22 / nginx / Docker)、 编译方法(npm ci + build)、四类部署方式(Docker+nginx 推荐路径、 静态服务器、对象存储/CDN、内网离线),含路径前缀要点与环境对照表 - 修复 docker/nginx.conf:base=/docs/ 使产物资源引用为绝对路径, 原配置从根路径服务导致资源全部 404(白屏)。改用 alias 剥离前缀, 并加 / -> /docs/ 重定向 - README 补部署指引与路径前缀告警,修正 dev 入口地址为 /docs/ - 导航与首页增加「部署手册」入口 --- .vitepress/config.mts | 12 +- README.md | 17 +- docker/nginx.conf | 13 ++ docs/deploy/index.md | 443 ++++++++++++++++++++++++++++++++++++++++++ docs/index.md | 2 + 5 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 docs/deploy/index.md diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 2bc0a76..a761e52 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -72,9 +72,19 @@ export default defineConfig({ { text: '文档首页', link: '/' }, { text: '使用手册', link: '/manual/' }, { text: 'OpenAPI', link: '/openapi/' }, - { text: '共享 Token 接口', link: '/openapi/operator/shared-token/' } + { text: '共享 Token 接口', link: '/openapi/operator/shared-token/' }, + { text: '部署手册', link: '/deploy/' } ], sidebar: { + '/deploy/': [ + { + text: '部署手册', + collapsed: false, + items: [ + { text: '部署手册', link: '/deploy/' }, + ] + }, + ], '/manual/': manualSidebar(), '/manual/web/': manualSidebar('web'), '/manual/mini-program/': manualSidebar('mini'), diff --git a/README.md b/README.md index 5657309..97dc030 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,11 @@ iotlcm-docs/ ```bash npm ci -npm run dev # http://127.0.0.1:5173/ +npm run dev # http://127.0.0.1:5173/docs/ ``` +> 入口地址带 `/docs/`:配置里 `base: '/docs/'`,直接访问根路径会 404。 + 构建与预览: ```bash @@ -45,15 +47,22 @@ npm run preview 站点部署在子路径 `/docs/`(见 `.vitepress/config.mts` 的 `base`)。本地开发时根路径会自动重定向 favicon。 -## 容器化部署 +## 部署 + +**完整部署手册见文档站:[部署手册](https://git.5iot.com/iotlcm-public/iotlcm-docs) → `docs/deploy/`**,包含推荐环境、编译方法、nginx / 对象存储 / 离线环境部署方式与常见问题。 + +最短路径(容器,推荐): ```bash -docker build -t iotlcm-docs . -docker run -d -p 8080:80 iotlcm-docs +docker build -t iotlcm-docs:$(git rev-parse --short HEAD) . +docker run -d --name iotlcm-docs -p 8019:80 iotlcm-docs:latest +# 访问 http://127.0.0.1:8019/docs/ ``` 多阶段构建:Node 阶段执行 `vitepress build`,最终镜像只含 nginx 与静态产物。 +> ⚠️ **路径前缀**:`base: '/docs/'` 使资源引用为绝对路径 `/docs/assets/*`,而 dist 是平铺的。`docker/nginx.conf` 已用 `alias` 剥掉前缀,**不要**改成从根路径直接服务产物,否则白屏。 + `docker-compose.yml` 里的镜像名需替换成你自己的镜像仓库地址;对外署时请用不可变标签(commit sha)而不是 `latest`。 ## 文档里的占位符 diff --git a/docker/nginx.conf b/docker/nginx.conf index 04e9641..4185448 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -5,6 +5,19 @@ server { root /usr/share/nginx/html; index index.html; + # 产物按 VitePress base=/docs/ 生成,HTML 里的资源引用形如 /docs/assets/xxx, + # 而 dist 目录是平铺的。因此必须在 /docs/ 路径下提供服务, + # 用 alias 把 /docs/ 前缀剥掉再落到产物目录,否则资源全部 404(白屏)。 + 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; } diff --git a/docs/deploy/index.md b/docs/deploy/index.md new file mode 100644 index 0000000..eb10bee --- /dev/null +++ b/docs/deploy/index.md @@ -0,0 +1,443 @@ +--- +title: 部署手册 +description: iotLCM 文档站的推荐环境、编译方法与部署方式 +outline: deep +--- + +# 部署手册 + +本页说明 iotLCM 文档站(`iotlcm-docs`)的运行环境要求、编译方法与部署方式。文档站是**纯静态站点**,由 VitePress 1.6 构建,产物不含任何服务端运行时。 + +::: tip 一句话结论 +生产推荐用 **Docker + nginx**(Node 只在构建阶段需要);只有一台机器、不想装 Docker 时,用 **Node 构建 + 任意 Web 服务器托管静态产物**。 +::: + +## 环境要求 + +### 构建环境(必需) + +| 项 | 推荐 | 最低 | 说明 | +| --- | --- | --- | --- | +| 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 | 纯静态,压力极低 | + +### 实测参考(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 +# 92f239f chore: 初始开源发布 +``` + +## ⚠️ 部署前必读:路径前缀 + +这是本项目最容易出错的地方,**部署失败九成是这个原因**。 + +文档站配置里 `base: '/docs/'`(见 `.vitepress/config.mts`),因此构建产物中所有资源引用都是**绝对路径**: + +```html + + +``` + +而产物目录 `.vitepress/dist` 是**平铺**的,`index.html`、`assets/` 直接在根,**没有 `docs/` 子目录**。 + +由此推出两条铁律: + +1. **对外访问路径必须是 `/docs/`**,且服务器必须把 `/docs/` 前缀剥掉后再落到产物目录 +2. 若想挂到别的前缀(如 `/manual/`),必须**先改 `base` 再重新构建**,不能只改服务器配置 + +::: warning 典型故障:白屏 +如果配置成「从根路径直接服务产物」(`root` 指向 dist,访问 `/`),页面 HTML 会返回 200,但 `/docs/assets/*.js` 全部 404 → **浏览器白屏,没有任何报错提示**。 +::: + +三种正确做法,任选其一: + +| 做法 | 适用 | 关键配置 | +| --- | --- | --- | +| nginx `alias` 剥前缀 | 容器部署(推荐) | `location /docs/ { alias /usr/share/nginx/html/; }` | +| 把产物放进 `docs/` 子目录 | 静态托管 / 对象存储 | `dist/` → `www/docs/` | +| 改 `base: '/'` 重新构建 | 独占域名根路径 | 改配置后必须重新 `npm run build` | + +--- + +## 推荐环境部署: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` 后缀,需要服务器做内部映射。 + +### 部署后自检 + +```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 +``` + +--- + +## 其他环境 + +### 方式一:本地开发与预览(改文档时用) + +```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` 文件会热更新,无需重启。 + +需要让局域网内其他机器访问时,把监听地址改为 `0.0.0.0`: + +```bash +npx vitepress dev . --host 0.0.0.0 +# 然后访问 http://<本机IP>:5173/docs/ +``` + +端口被占用时换端口: + +```bash +npx vitepress dev . --port 5174 +``` + +### 方式二:构建静态产物 + 任意 Web 服务器 + +这是**不使用 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`(或反之),一定会白屏。 +::: + +#### 部署到对象存储 / 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` 重新构建 + +### 方式三:内网 / 离线环境 + +机器无法访问公网 npm 源时: + +**方案 A:使用内网镜像源** + +```bash +npm ci --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 +``` + +**方案 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 +``` + +::: tip 已内置国内 apk 源 +`Dockerfile` 的 builder 阶段已把 Alpine 源换成 `mirrors.aliyun.com`,无需额外处理。 +::: + +### 方式四:pnpm / yarn + +仓库同时提供 `package-lock.json` 与 `pnpm-lock.yaml`。使用 pnpm: + +```bash +pnpm install --frozen-lockfile +pnpm run build +``` + +npm 与 pnpm 二选一即可,**不要混用**(会产生不一致的 `node_modules`)。 + +--- + +## 环境对照表 + +| 场景 | Node | Docker | 访问路径 | 关键动作 | +| --- | --- | --- | --- | --- | +| 本地改文档 | 必需 | 不需要 | `/docs/` | `npm run dev` | +| 生产(推荐) | 仅构建阶段 | 必需 | `/docs/` | `docker build` + nginx alias | +| 静态托管(子路径) | 仅构建阶段 | 不需要 | `/docs/` | 产物放 `docs/` 子目录 | +| 静态托管(根路径) | 仅构建阶段 | 不需要 | `/` | 改 `base: '/'` 重新构建 | +| 对象存储 / CDN | 仅构建阶段 | 不需要 | `/docs/` | 上传 + 配 404 与默认首页 | +| 内网离线 | 可在别处构建 | 可选 | `/docs/` | 只拷产物或改 FROM | + +## 常见问题 + +| 现象 | 原因 | 处理 | +| --- | --- | --- | +| 页面白屏、无样式 | `/docs/` 前缀未剥离,资源 404 | 用 `alias`,或产物放 `docs/` 子目录 | +| 访问 `/` 404 | `base: '/docs/'` | 访问 `/docs/`;容器版已自动 302 | +| 访问 `/docs`(无末尾斜杠)404 | 未配置重定向 | 加 `location = /docs { return 301 /docs/; }` | +| 正文页 404、首页正常 | 缺 `$uri.html` 映射 | `try_files` 补 `$uri.html $uri/index.html` | +| `npm ci` 报 lock 不一致 | Node 版本过低 | 升级到 Node 22 | +| 构建报内存不足 | 容器内存限制过小 | 给到 ≥ 1 GB | +| 端口 5173 被占用 | 已有 dev 进程在跑 | `--port 5174` 或结束旧进程 | +| 反向代理后白屏 | `proxy_pass` 带了末尾斜杠 | 去掉末尾斜杠 | + +## 版本固定 + +开源仓按**固定版本**发布(无 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 +``` + +## 相关文档 + +- [使用手册](/manual/) —— Web 管理端与小程序端操作说明 +- [OpenAPI 接口文档](/openapi/) —— V3 接口鉴权与调用说明 diff --git a/docs/index.md b/docs/index.md index 2adf626..bb46849 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,4 +23,6 @@ features: details: 客户开放接口的鉴权方式、请求参数、响应结构、调用示例与兼容接口说明。 - title: 共享 Token 接口文档 details: 共享 Token 获取与白名单转发说明。 + - title: 部署手册 + details: 文档站的推荐环境、Node/Docker 编译方法、nginx 与对象存储部署方式,以及路径前缀配置要点。 ---