无尘阁日记

无尘阁日记

跟GPT合作给同事写一个查看版本等的小功能
2026-01-21

今天这个功能,表面看起来很简单:
“项目版本中心,能看到 OA 推送历史,并能把相关底稿、报告、说明下载下来。”

但真正动手以后,你会发现它是一个典型的“企业级泥潭功能”
不是算法难,不是框架难,而是现实复杂度极高

我把这次 projectVersionCenter 的共创,拆成 6 个阶段。

一、需求不是“功能”,而是一坨历史沉积物

一开始,并不存在一个清晰的“我要做什么”。

你拿到的是:

  • OA 推送日志表(system_log)

  • 推送详情表(system_log)

  • 文件相关表(tianjian202-proapi)

  • FileHistory / FileHistory_V2 / SingleAsset

  • OssFileRecord

  • ReportsProjects

  • Projects

  • 各种 “这个字段现在不用了”

  • 各种 “这个字段以前是 A,现在是 B”

这不是一个新功能,这是一个考古现场

👉 第一个关键判断

这类需求,千万不要一上来写代码。
你写得越快,返工越惨。

我们做的第一件事,不是写 SQL,而是把真实数据流画出来

二、先做“事实层建模”,而不是代码建模

我们明确了一个非常重要的事实:

1️⃣ 日志和业务不在一个世界

  • system_log

    • 只关心:推没推、推给谁、推成功没、要不要重试

  • tianjian202-proapi

    • 才是真正的业务世界(项目、底稿、报告、文件)

这一步如果不想清楚,后面一定会写出“逻辑上成立、业务上错误”的代码。

2️⃣ “文件”不是一个表,而是一个路径系统

这次最容易踩坑的一点是:

你以为你在连“文件”,
实际上你在穿越 三套文件体系的历史演进

我们最终确认了三条真实链路

  • 收益法 / 市场法 / 单项资产

    detail.draft_id
        → TJ_FileHistory(_SingleAsset)
            → FileId
                → TJ_Files.Uuid
                    → Path
  • 资产基础法

    detail.draft_id
        → TJ_FileHistory_V2
            → FileSignCompound
                → TJ_OssFileRecord.FileSign
                    → ServerPath
  • 报告 / 说明

    detail.related_report_id
        → TJ_ReportProjects.UUID
            → ReportFileFinal

👉 这是这次功能最重要的“知识资产”
比任何代码都值钱。

三、跨库不是难点,跨“字符集世界”才是

当 SQL 第一次跑起来报错的时候,错误是这个:

Illegal mix of collations (utf8_general_ci) and (utf8_unicode_ci)

这是一个非常典型、但被严重低估的企业系统问题

表象

  • SQL 写得没错

  • 表也存在

  • 数据也在

真正原因

  • 不同年代建的表

  • 不同团队

  • 不同默认 collation

  • UUID / FileId / FileSign 都是 varchar

一旦跨库 JOIN,MySQL 就会拒绝帮你“猜规则”。

我们做出的工程选择(非常关键)

👉 不改表结构,不搞大手术
👉 在 JOIN 层显式控制规则

也就是这一类写法:

f_income.Uuid COLLATE utf8_unicode_ci
    = fh_income.FileId COLLATE utf8_unicode_ci

这是一个非常成熟、现实的选择:

  • 不影响现有系统

  • 不锁表

  • 不引入连锁风险

  • 成本极低

这是典型的 “工程解法,而不是教科书解法”

四、工具链细节,也会在真实项目里绊你一跤

一个非常真实、但只有在老项目里才会出现的小插曲:

static fn($v) => ...

PHP 7.1 不支持。

这不是水平问题,这是现实环境问题

你如果习惯写现代 PHP,很容易下意识用箭头函数;
但在企业项目里,“环境约束”永远高于“语言优雅”

最终我们选择:

$h = static function ($v) {
    return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
};

可运行 > 优雅

五、projectVersionCenter 真正“值钱”的地方

这个功能真正的价值,并不只是“能下载文件”。

而是它做了三件以前系统里做不到的事:

1️⃣ 把“推送行为”还原成“可追溯事实”

  • 谁推的

  • 推了什么

  • 推给谁

  • 成功 / 失败

  • 重试了几次

  • request / response 全量保留

这是审计级能力

2️⃣ 把“散落在各处的版本实体”拉回一个中心

以前是:

  • 底稿在一套逻辑

  • 报告在一套逻辑

  • 说明在一套逻辑

  • OA 只知道“我推过”

现在是:

围绕 project_version 这个视角,把所有产物重新聚合。

这才叫 Version Center。

3️⃣ 顺手解决了“权限外分享”的密码问题

TJ_Projects.UUID 生成:

md5(UUID + 3)

这一步看似不起眼,但它意味着:

  • 版本中心 ≠ 内部系统

  • 它已经在为“跨系统访问 / OA / 外部协作”做准备

这是架构意识,不是功能意识。

六、这次共创过程,最值得别人借鉴的 5 条原则

最后,给其他读者一个可直接复用的 checklist

✅ 1. 先画真实数据流,再写一行代码

不画清楚,后面所有 bug 都是必然的。

✅ 2. 不要迷信“表名”,要看字段真实语义

同叫 FileId,可能是:

  • 文件主键

  • 历史表主键

  • OSS 签名

  • UUID

✅ 3. 老系统里,JOIN 的敌人不是性能,是字符集

遇到 collation,优先 JOIN 层解决

✅ 4. 能在 SQL 里解决的,不要推给 PHP

路径拼接、文件定位、过滤,SQL 能干的尽量干。

✅ 5. Version Center 是“治理能力”,不是页面功能

它解决的是:

  • 混乱

  • 追责

  • 历史不可见

  • 版本不可复现

结语

今天这个 projectVersionCenter,不是写了多少行代码,
而是把一个长期被忽略的“版本真相”重新组织了一次

这种功能,往往不会在发布会上被提到,
一旦出问题、查历史、要追责,它就是救命的东西。

如果你正在维护一个年头很久、逻辑很杂、谁都不敢动的系统
希望这次复盘能让你意识到一件事:

真正的工程能力,
不在“写新功能”,
而在让旧世界重新变得可理解。