Files
lexgo/docs/11-deployment-and-operations.md

13 KiB
Raw Permalink Blame History

generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Deployment-and-Operations wiki_url: https://git.ilapage.cn/OPC/lexgo/wiki/Deployment-and-Operations.- wiki_revision: 2fa9c30e08fc14346d9960178a425a8ee3564a3a synchronized_at: 2026-09-16T05:27:09Z

部署与运维

本页面向 LexGo 的内部维护者:在一台干净的机器上首次部署、日常运维、健康检查、升级回滚与备份恢复。所有命令以仓库根为工作目录。本页只写配置项名称与来源,不写任何真实密码、令牌或生产数据库地址。

本页用途

让维护者在一台新机器上完成首次部署,并在需要时把实例从备份恢复回来,同时知道哪些步骤不可逆、哪些证据必须留下。

安全边界

  • 凭据只从运维密码库或 .env.local(已被 .gitignore 忽略)读取;.env.local 权限应为仅本人可读写。
  • 备份文件含全部用户数据(账号、书籍原文、词条、复习记录、审计日志、词典归档),必须按个人数据对待:存放位置受控、不进入 Git、不进入工单附件。
  • restore 会写入数据库:默认只写空库,覆盖已有库必须显式 --force,任何情况下都必须 --confirm。
  • 服务默认只监听 127.0.0.1;对外提供访问时经反向代理,不要直接暴露应用端口。

服务概览

项目 内容
组件 Go 后端(server/)、学习端 SPA(learner/)、管理端 SPA(admin/)、MySQL 8
后端监听 LEXGO_LISTEN,默认 127.0.0.1:8000
前端托管 反向代理(nginx)托管已构建的 dist,同一域名下把 /api/ 转发到后端
数据库 MySQL 8(本机验证版本 8.4.3);库名经 LEXGO_DB_NAME 指定
运行时 后端为单个静态二进制,无需运行时依赖;构建需要 Go 1.26.5、Node 22、pnpm 9
进程托管 supervisor(本项目开发机即为 lexgo-api / lexgo-learner / lexgo-admin 三个 program)
日志 supervisor 的 stdout/stderr 日志文件;应用自身不写文件日志
备份对象 MySQL 全库(用户数据、原文、词典归档、审计、书籍封面以及章节音频/插图与播放位置)+ .env.local(单独从密码库取)

环境要求

组件 版本要求 检查命令 预期结果
操作系统 Windows 或 Linux — 本项目在 Windows 开发机验证;Linux 步骤为等价命令
LexGo 二进制 与数据库 schema 版本匹配 ./lexgo verify --database <库名> 校验通过;它自身会拒绍非 MySQL 8
Python 3.8+,仅工具通道需要 python scripts/ops.py install-check 纯二进制路径(lexgo ...)不需要 Python;Python 通道用于开发便利与交叉验证
MySQL 服务端 8.x mysql --version(服务端 SELECT VERSION()) 8.4.3 已验证
MySQL 客户端 不低于服务端 python scripts/ops.py install-check 「客户端版本不低于服务端」为 ok;5.7 客户端连 8.4 服务端会被判 fail
Go 1.26.5 go version 供 scripts/server.py 固定工具链构建
Node / pnpm Node 22、pnpm 9 node --version、pnpm --version 仅在需要构建前端时要求
WordNet 资源 server/wordnet-resource.json 固定的 ZIP python scripts/ops.py install-check 「WordNet 资源 pin」为 ok;资源只在显式导入时使用,不在运行时下载

首次部署

1. 取得代码并检查依赖

git clone <仓库地址> <部署目录>
cd <部署目录>
git rev-parse HEAD          # 记录本次部署的提交哈希
python scripts/ops.py install-check

预期结果:提交哈希被记录到部署记录;install-check 全部 [ok],否则按其提示补齐后再继续。

2. 创建数据库与配置

python scripts/ops.py init-database --database lexgo_prod

预期结果:输出「已创建库」并打印需要授予的最小权限(SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP, REFERENCES,仅限该库)。不要用管理员账号运行应用。

把 .env.local(从 .env.example 复制)填好:LEXGO_DB_HOST/PORT/NAME/USER/PASSWORD、LEXGO_BOOTSTRAP_USERNAME/PASSWORD、LEXGO_LISTEN。凭据值取自运维密码库,不进 Git、不进日志。

3. 迁移与初始化管理员

纯二进制路径(推荐,部署机只需要二进制与 MySQL 客户端):

./lexgo migrate         # 只有这一步会改表结构
./lexgo bootstrap       # 首次建立唯一管理员

开发便利路径(等价,额外做两件事:加载 .env.local、固定 Go 工具链):

python scripts/server.py migrate
python scripts/server.py bootstrap

预期结果:migrate 输出 schema 版本;bootstrap 输出创建成功。库中已有账号时 bootstrap 会拒绝执行,不会覆盖既有管理员。空库不含默认密码与任何演示数据。两种路径共用同一份 LEXGO_* 配置。

4. 构建产物

python scripts/server.py build         # 产出 server/lexgo(.exe)
cd learner && pnpm install && pnpm run build
cd ../admin && pnpm install && pnpm run build

预期结果:后端二进制与两份 dist 生成。前端由反向代理托管;反向代理需把未知路径回落到 index.html(SPA 路由),并把 /api/ 转发到后端。

5. 词典资源

词典归档在显式导入后存于 MySQL,备份与恢复会一并带走,运行时不下载。

6. 启动与健康检查

按 supervisor 配置启动三个 program(后端 + 两个静态站点或由反向代理托管)。然后执行下方「健康检查」全部命令。

配置与凭据来源

配置项 用途 来源 是否敏感
LEXGO_DB_HOST/PORT/NAME/USER/PASSWORD 数据库连接 .env.local,值取自运维密码库 是(密码)
LEXGO_BOOTSTRAP_USERNAME/PASSWORD 首次建立管理员 同上 是(密码)
LEXGO_LISTEN 后端监听地址 .env.local 否
LEXGO_MYSQL_BIN 指定 MySQL 客户端目录 运维环境变量 否
LEXGO_TEST_DB_NAME 集成测试库(仅开发) 环境变量 否

scripts/ops.py 的 manifest 与日志只记录配置项名称、库名、schema 版本与提交哈希,不记录任何凭据值。

日常运维

操作 命令 预期结果
查看状态 supervisorctl status lexgo-api RUNNING,uptime 持续增长
重启后端 supervisorctl restart lexgo-api stopped 后 started
查看日志 supervisorctl tail -f lexgo-api stderr 持续输出应用日志
备份 ./lexgo backup --out <目录> 生成 lexgo-<时间戳>.sql.gz 与 manifest.json
校验实例 ./lexgo verify --database <库名> 打印每项检查结果,最后「verification passed」
清理过期审计 python scripts/server.py audit-cleanup 只清理两张审计表中超过 90 天的记录
关闭确认 supervisorctl stop lexgo-api 服务停止;学习端与反代仍在,接口不可用

健康检查

supervisorctl status lexgo-api
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/healthz
python scripts/ops.py verify --database <库名>

预期结果:状态 RUNNING;curl 返回 200;verify 全部 [ok]。任何一项不符合时不视为部署成功,按「升级与回滚」处理。

升级与回滚

升级

git rev-parse HEAD                       # 记录升级前提交,回滚需要
python scripts/ops.py backup --out <目录> # 有迁移时必须先备份
git fetch --all && git checkout <目标提交或标签>
python scripts/server.py build
cd learner && pnpm install && pnpm run build && cd ../admin && pnpm install && pnpm run build
python scripts/server.py migrate         # 只有迁移单需要
supervisorctl restart lexgo-api

预期结果:重启后状态 RUNNING,健康检查全部通过。前端 dist 更新后由反代直接生效,无需重启后端。

回滚

git checkout <升级前记录的提交>
python scripts/server.py build
supervisorctl restart lexgo-api

预期结果:健康检查全部通过。

注意:已执行的迁移通常不能用切回代码撤销。LexGo 的用法是:每个版本的迁移只新增对象(CREATE TABLE IF NOT EXISTS),旧二进制认版本号,因此回退到旧二进制前要把 lexgo_schema.version 写回旧版本号;若迁移删改了数据或旧二进制无法读写新结构,则按「备份与恢复」处理。本节只描述规则,真实回滚尚未演练过,属于已知限制。

备份与恢复

备份对象与频率

  • 数据库:一个实例的全部持久数据(账号与会话、书籍与章节原文、导入任务、词典归档、词条、复习排期与作答、阅读进度、审计日志)。
  • 环境配置:.env.local(含凭据)单独从运维密码库保存,不放进备份目录。
  • 代码与二进制:由 Git 提交哈希重建,manifest 中记录了该哈希。
  • 建议频率:每次升级或有迁移前必须备份;日常按使用强度自行决定(本项目未启用定时任务)。保留份数与存放位置由运维决定。
./lexgo backup --out <备份目录>

预期结果:目录中出现 lexgo-<时间戳>.sql.gz 与 manifest.json(schema 版本、提交哈希、逐表行数、dump 的 sha256、客户端与服务端版本)。manifest 不含凭据。

备份与校验也可以走 Python 工具通道(python scripts/ops.py backup|restore|verify)。两条通道的 dump 与 manifest 格式完全相同,可以互相读取:2026-09-15 已交叉验证—— Go 产出的备份用 Python 恢复、Python 产出的备份用 Go 恢复,两个恢复实例的逐表行数与内容校验和都与源库一致。

恢复步骤

./lexgo restore --dump <备份目录>/lexgo-<时间戳>.sql.gz --database <新库名> --confirm
./lexgo verify --database <新库名> --manifest <备份目录>/manifest.json

预期结果:恢复写入空库并自动校验通过;verify 打印完整性与逐表行数比对,最后「verification passed」。恢复不会写入备份里的源库,工具会在恢复前后比对源库的内容校验和(本机演练已验证)。

接口级的两账号闭环验证仍在 Python 工具通道(它需要发 HTTP 请求):

python scripts/ops.py verify --database <库名> --api http://127.0.0.1:<端口> --user <账号前缀> --password-env <变量名>
python scripts/ops.py smoke --api http://127.0.0.1:<端口> --admin-user <管理员> --user <前缀>

注意:restore 默认拒绝写入已有数据的库;覆盖必须 --force,执行前先备份当前库。库名必须包含 lexgo 且不能是 MySQL 系统库。

恢复演练记录

2026-09-15 在本机完成完整演练:空库安装(init-database → migrate → bootstrap → 管理员建两个演练账号 → 走通粘贴/阅读/查词/保存/复习/完成章节/进度)与「备份 lexgo_dev → 恢复到空库 → 起第二个 API 实例 → 两账号闭环与越权校验」,恢复后逐表行数与内容校验和都和源库一致,演练库用后删除。详细命令与结果见工单 #15 与本地开发页。

已知限制

  • 真实回滚演练未做:本文档给出规则,但没有在真实实例上执行过「升级 → 回滚」全过程。
  • 未验证 HTTPS、域名、多机与灰度部署;本机演练只用 127.0.0.1 与模拟触摸视口。
  • 未启用定时备份、监控与告警;备份由人工触发。
  • 封面(书级)与音频、插图(章级)都存进数据库,因此 dump 自动包含它们与播放位置,恢复后仍可按字节读取并拖动;附件会让 dump 变大(单音频上限 20 MiB,图片上限 2 MiB),容量与备份体积要按试用量估算。#37 起附件挂在章节上,书级音频接口已下线。
  • 附件读取需要会话并支持 HTTP Range;客户端用带凭据的 fetch 取字节后交给媒体元素,令牌不出现在 URL 里,但浏览器会整份取回后再播放(未做渐进式流式播放)。
  • 恢复演练是单机顺序执行,未验证大库恢复耗时与磁盘空间上限。
  • 接口级的两账号闭环与越权验证需要 HTTP 客户端,目前只在 Python 工具通道提供;Go 二进制提供数据库层的备份、恢复与校验。
  • 备份仍调用 mysqldump:自己实现一致性导出风险更高,因此部署机需要 MySQL 客户端而不只是服务端。

备份体积与词典资源(#40)

英汉词典归档(约 3.0 MB)与 WordNet(约 10.3 MB)都以整包形式存在 lexgo_dictionaries.archive, 因此 lexgo backup 的 dump 天然包含两本词典,恢复后无需重新下载或重新准备词典。

  • 备份/恢复流程与 #15 相同,不需要新增参数;lexgo verify 检查的仍是同一套清单。
  • 词典归档由 scripts/dict_prepare.py 在本机从 sha256 pin 的源生成,产物不入库; 恢复演练时若需要重新准备,必须重新下载并在准备阶段校验 sha256。
  • 迁移到 v12 后旧二进制会被拒绝启动(见本地开发页),因此回退二进制必须先回退 schema 版本标记。
  • 未做:定时备份、多主机、HTTPS 的缺口沿用 #15 记录,不因本单变化。