Files
xiaotutongxue/backend/xt-server
lovife 80a4050e53 feat(file): 材料上传与规范化(压缩到 1M 内 + 按规则改名)
这是产品的核心资产。实测那批真实材料里 **52.7% 的原始文件名是违规的**
(`曾伊豪-...110身份证.jpg.jpg` 这种),而认定机构只看两件事:
**体积有没有超 1M**、**名字对不对得上人**。这两件事做不对,
材料会被整批退回 —— 而退回时往往已经截止了。

两步走,不能合成一步:
 ① upload:先原样存下来。这一步必须快、必须成功 —— 学生的手机可能是慢网络,
    上传中断是最常见的失败。先落盘,学生就不必重传。
 ② process:压缩 + 改名。慢(要解码、试多档质量),但在服务端跑,学生不用等网络。

压缩策略:**先保清晰度,再压质量,最后才缩尺寸**。顺序不能反 ——
证件类的可读性是底线(机构要能看清号码),缩过的证件翻拍件数字就糊了。
实测:11.6MB 的 2400x3200 原图 → **712KB**,1020x1360(P2 长边限 1600),q70。

命名策略:模板 + 白名单 + 非法字符清理。
  ⚠️ 非法字符清理不能省:学生的原始文件名里 `/` 很常见("学籍表(1/2)"),
     而带 `/` 的名字**根本写不进磁盘** —— 表现是"保存失败",
     而错误信息不会告诉你是因为文件名里有个斜杠。

原始文件**绝不覆盖**(另存 std/):认定机构退件时我们要能拿出原始件,
覆盖掉原图就再也说不清了("是不是你们压坏的")。E2E 断言了原图一字不差。

⚠️ 与契约的一处偏差(已知、有意):契约写的是 POST /mp/files/upload-token
   (OSS 直传凭证),当前**后端代理接收**(multipart)。原因:后端还没接对象存储,
   直传凭证要先有 bucket 与签名机制。硬做一个"返回假凭证"的接口只会让人以为能直传。
   接入对象存储后可以并存:小文件走代理、大文件走直传。

⚠️ 当前把文件存在**本地磁盘**(fill.upload.dir)。多实例部署时各存各的会丢文件,
   所以接入对象存储前**不能上多副本**。这一点必须记住,别当成"已经解决了"。

E2E 里修了一个夹具陷阱(值得记):用**裸 SQL** 插材料项会绕过 MaterialCatalog
   的默认值(DDL 列默认是 AUTO / 1 份),于是"档位没生效""份数不对" ——
   而这两条都会被误当成代码 bug。实测就踩了:IDCARD 期望 P2 + 2 份,
   裸 SQL 插出来是 AUTO + 1 份,AUTO 的行为(不缩放、先降质量)被当成了"P2 没生效"。

细节修正:文件名里的脱敏号用 X 而不是下划线。`*` 在 Windows 文件名里非法,
   被 sanitize 换成下划线后 `361029******0015` 变成 `361029_0015` ——
   没人看得出那是脱敏号。现在是 `361029XXXXXX0015`。

验收:e2e-m3-file.py 29/29(含体积、档位、命名、原图保留、幂等、白名单、份数)。
2026-09-29 11:49:21 +08:00
..