5
Deployment-and-Operations
ila edited this page 2026-09-16 13:26:44 +08:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

部署与运维

本页面向 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 记录,不因本单变化。