平台优化日志|后端健康探针上线与仓库凭据治理
补齐真实健康探针,并完成一次覆盖全仓库历史的凭据与隐私数据治理。
- 事件类型
- 复盘总结
- 规模
- 公司级
- 事件日期
- 2026 年 9 月 14 日
工作小结
一、这次做了什么
两件事,起因不同,但都指向同一个问题:公司自有平台的"可观测性"和"凭据卫生"欠账太久。
慧玩智家云施工管理系统是公司自有的业务平台,承载着项目、客户、合同、库存、设计等核心数据,服务对象是公司业务团队与客户。正因为它是自有平台、数据是公司资产,这两件事才有必要认真做——不是"能用就行",而是要能长期守住。
1. 补上真实的健康探针
系统一直有一个 /health 地址,网关也配了转发,看起来像模像样。但这次排查发现:后端根本没有这个路由,请求打到服务端后被单页应用的兜底逻辑接住,返回的是一张 HTML 页面。
也就是说,这个探针从来没有真正探测过任何东西 —— 服务挂了它返回 200,数据库挂了它还是返回 200。一个永远说"正常"的探针,等于没有探针。
现在补上了真端点,返回服务名、运行时长,并实际执行一次数据库查询:
{
"status": "ok",
"service": "smartjoy-home-build-backend",
"uptime": 20,
"uptime_human": "20s",
"database": "up",
"timestamp": "2026-09-14T03:10:31.063Z"
}
database 字段是关键 —— 我们这台服务器没有交换分区,内存不足时内核会直接终止占用最大的进程,数据库正是最大的目标之一。服务进程活着而数据库已死是这台机器上真实存在的故障形态,探针必须能分辨这一种。
2. 清理仓库中已经存在很久的凭据与隐私数据
排查硬编码密码时,顺手做了全仓库的历史扫描,结果比预想严重得多:
| 项 | 实际数量 |
|---|---|
| 含明文密码的文件(全历史) | 18 个 |
| 涉及提交 | 14 个以上 |
| 牵连的隐私数据 | 真实手机号、真实邮箱、真实住址 |
| 最严重的一项 | 一份 8.2 MB 的整库备份,含 619 位客户的姓名、电话、地址 |
其中客户数据是公司的核心资产,也是公司对客户的承诺 —— 客户的姓名、电话、住址不该出现在任何代码仓库里,哪怕是私有仓库。
处置分三步走:加密留存 → 工作区清理 → 历史重写。
二、过程中真正值得记的几件事
1. "发现两个文件"不等于"只泄漏了两个文件"
最初的判断是两个一次性调试脚本里写了密码。范围是要量出来的,不是想起来多少算多少。
一条命令把范围摊开:
git log --all --pretty=format: --name-only -S "<敏感串>" | sort -u | grep -v '^$'
结果从 2 个文件变成 18 个。凭据会顺着"项目整体导出""文档快照""数据库备份"这些路径扩散,不会老实待在源码里。
结论:一发现凭据问题,先量全量范围,再谈怎么处置。
2. 只清理一个分支等于没清理(本次最大的坑)
历史重写跑完三轮,主分支验证全部归零,看起来收工了。收尾时多问了一句——远端还有别的分支吗?
git ls-remote --heads origin
结果:远端除了主分支,还有一个 master 分支保有完整的旧历史,而且它和主分支没有共同祖先(完全独立的分叉)。那个分支里,密码出现 14 个提交、手机号 25 个提交、数据库文件 27 个提交 —— 全在。
含义很直白:历史重写只作用于"当前分支及其可达历史",其他分支的独立历史原样保留。前面三轮全部白做,因为任何人拉取代码后切到那个分支就能拿到全部明文。
删除该分支后,才算真的清干净。这是一个必须靠主动盘查才能发现的遗漏 —— 如果只验证当前分支,所有检查都会显示通过。
3. 本地仓库的验证结论不可信
三轮重写之后在本地跑搜索,报告"手机号仍有 8 个提交命中",一度以为重写失败。实际原因是本地残留的旧分支造成的假阳性——远端早已干净。
验证必须在全新克隆上做,不能在本地工作副本上做。 这跟"改完必须实测线上"是同一条纪律:验证环境的干净程度,决定了验证结论有没有意义。
4. 权限类改动要验"反向项"
加 .gitignore 拦不住已经被跟踪的文件 —— 这是常识,但容易在操作中忘掉。做法是:git rm --cached 保留工作区文件 + .gitignore 阻断未来提交,然后提交后扫一遍暂存区,证明真的没进去:
git diff --cached --name-only | grep -E "\.sql$|\.backup$|\.env$" && echo "有凭据被暂存!" || echo "无凭据入库"
注意 git status 里的假阳性:删除记录是 D(正确),新增是 A(危险),要看第一个字母。
"我加了配置"不等于"配置生效了" —— 安全类改动必须配一条能够失败的检查。
5. 加密包入库,但密码不进仓库
技术负责人定的处置方式是"文件压缩加密后仍然放在仓库里"——这既保留了"一键恢复"的便利,又消除了明文风险,是比"移出仓库"更贴合实际的选择。
密码与加密包必须分开放:
- 加密包(AES-256,文件名一并加密)入库
- 密码写进服务器受保护路径(
600权限),不进任何仓库 - 仓库里的说明文档只写"密码见服务器受保护路径或询问技术负责人"
锁和钥匙放一起,加密就没有意义。 这一点在处置过程中必须主动提出,不能把密码照抄进说明文档。
加密完成后的验收标准也不是"能解开",而是解出来的和原件逐字节一致——本次对全部文件做了双向校验,全部通过。
三、一个与此无关、但顺手修掉的问题
健康探针加好、部署完成、服务日志里明确显示路由注册成功(Mapped {/health, GET}),但访问仍然返回 HTML。
根因不在探针本身:单页应用的历史回退中间件注册在框架路由之前,而它的放行条件只覆盖"以 /api 开头"和"带文件扩展名"两类。/health 两者都不符合,于是被兜底逻辑抢先截住,返回了首页。
这类问题的迷惑性在于日志说"成功"、状态码说"200"——两项都在骗人。判据要加第三条:
curl -s -o /dev/null -w 'code=%{http_code} type=%{content_type}\n' http://127.0.0.1:8172/health
# 期望 code=200 type=application/json ← 类型必须是 json,不能是 text/html
只看状态码会漏,因为兜底返回的 HTML 也是 200。
四、沉淀下来的通用规则
| # | 规则 |
|---|---|
| 1 | 安全问题的范围要量,不要凭印象——第一眼看到的往往是最小值 |
| 2 | 历史重写是全仓库动作,不是单分支动作;动手前列全部分支,收尾逐分支确认 |
| 3 | 验证要在全新克隆上做,本地残留状态会制造假阳性 |
| 4 | 权限/保护类改动必须配反向检查——能证伪才算验证过 |
| 5 | 敏感文件与解密密码分离存放;文档里绝不出现明文密码 |
| 6 | 验收看内容类型/唯一标识,不只看状态码——200 无法区分新旧代码 |
| 7 | 服务器上的部署副本不是代码仓库时,不受历史重写影响——动手前先确认,能省一整块工作 |
五、数据口径
| 项 | 结果 |
|---|---|
| 历史重写轮次 | 3 轮(路径抹除 → 字符串替换 → 补抹副本) |
| 远端分支处置 | 删除 1 个残留旧分支 |
| 终验(全新克隆) | 4 类敏感字符串 + 7 个敏感文件 全部 0 命中 |
| 加密包校验 | 全部文件解密后 MD5 与原文件一致 |
| 健康探针 | /health 返回 JSON,含数据库连通性 |
重写前的完整镜像备份已留存,可随时还原至操作前状态。
本文由数字员工整理,技术负责人审定。