feat(h5): 学生端材料预览 + 删掉部署里的 MinIO

一、部署配置清理
  docker-compose.yml 里的 MinIO 删了(连 minio-data 卷)。它当初是
  "M0 阶段不写入但先起好 —— 本地/生产走同一套 S3 接口",但代码最终走
  七牛官方 SDK,那个容器**从来没被连过** —— 留着只会让人以为"本地还有个
  对象存储要用"。.env.example / .env 的 MINIO_* 换成 QINIU_* + XT_STORAGE_*。

  ⚠️ 删的时候我自己犯了个错:用 volumes:\n 当插入锚点,结果匹配到了
     **pg 服务内部的 `  volumes:`**,把注释插进 pg 定义里还把它顶到第 0 列,
     **YAML 结构就这么坏了**。好在写完立刻用 yaml.safe_load 复查(services: [pg, redis])
     才发现。教训:改 YAML 之后一定要解析一遍,别靠肉眼看缩进。

二、学生端材料预览(新功能)
  已传材料每行加「预览」,点开全屏浮层(深底 —— 材料照片多是白底证件件,
  浅底会看不清边界),点图片以外关闭。

  **两种存储两种取法,不能只写一种**:
    · 七牛:接口返回带签名的绝对 URL,直接给 <image src> 用
    · 本地磁盘:那个接口要带 Authorization,而 <image src> 发不了请求头 ——
      先用 uni.request 取字节,再转 blob URL
  只写七牛那一种,本地开发时预览会一片空白,而**原因完全看不出来**。
  签名链接有有效期(30 分钟),所以每次预览都现取,不缓存 URL。

  顺手修两个后端问题:
    · LocalStorageService.url() 返回的路径指向一个**不存在的接口**
      (我建的是 /{id}/content,不是 ?path=)→ 改由 MaterialFileService 按
      provider 决定:七牛给签名链接、本地给 /{id}/content(按 id 取能复用归属校验)
    · /content 原来固定 application/octet-stream —— 表现是**点开身份证照片
      变成下载一个文件**。现在按文件名推 Content-Type + inline

三、浏览器实测
  点预览 → 浮层打开、图片真的解码(naturalWidth=900、complete=true);
  直接 fetch 那个签名 URL → 200 / 17728 字节。
  中间发现一个视觉问题:**浮层标题与「关闭」被 uni-app 导航栏盖住了**
  (page-head 的 z-index 是 998,我给的 95)—— 看起来像"标题没渲染出来"。
  改成 9999 + padding-top 让出导航高度;问题清单浮层一并提到 9998。复验通过。

四、顺带
  dev-seed-warn-state.py 改为没有可用草稿时自己建一份 —— 原来写死 submission id,
  被 E2E 清理后就报"填报单不存在或不属于你",而那看起来像权限问题。

验收:e2e-m3-file 32/32、m3-submit 31/31、m1-storage 16/16
This commit is contained in:
2026-09-29 15:16:36 +08:00
parent d8463a051f
commit fe7615d12b
7 changed files with 263 additions and 40 deletions
+65
View File
@@ -1334,3 +1334,68 @@ m1-materials 68 / m1-form 48 / m1-task 71 / m1-orgs 48
`_selfcheck/`、`_probe_/`)。**这是刻意的**:原始件要留着备查。
要清就在七牛控制台按前缀清,或跑一次带清理的脚本。
- `backend/data/uploads/` 里有本地磁盘时代的旧测试文件(已 gitignore),可随时删。
---
## §一·十九 部署配置清理 + 学生端材料预览
### ① 部署配置:删掉 MinIO
`deploy/docker-compose.yml` 里的 MinIO 删了(连带 `minio-data` 卷)。
它当初是"M0 阶段不写入,但先起好 —— 本地/生产走同一套 S3 接口",
但代码最终走的是**七牛官方 SDK**,那个容器**从来没被连过** ——
留着只会让人以为"本地还有个对象存储要用"。已在文件头写明原因。
`.env.example` / `.env` 里的 `MINIO_*` 换成 `QINIU_*` + `XT_STORAGE_*`。
> ⚠️ 我自己在删的时候犯了个错,值得记:用 `volumes:
` 当锚点插入说明,
> 结果匹配到了 **pg 服务内部的 ` volumes:`**,把注释插进了 pg 的定义里,
> 还把它顶到了第 0 列 —— **YAML 结构就这么坏了**(`pg.volumes` 变成顶层键)。
> 好在写完立刻用 `yaml.safe_load` 复查了一遍(`services: [pg, redis]`)才发现。
> **教训:改 YAML 之后一定要解析一遍,别靠肉眼看缩进。**
### ② 学生端材料预览(新功能)
已传材料每一行现在有「预览」,点开是全屏浮层(深底 —— 材料照片多是白底证件件,
浅底会看不清边界),点图片以外的地方关闭。
**两种存储两种取法,不能只写一种**:
- **七牛**:接口返回**带签名的绝对 URL**,直接给 `<image src>` 用;
- **本地磁盘**:那个接口要带 `Authorization`,而 `<image src>` **发不了请求头** ——
所以先用 `uni.request` 把字节取回来,再转成 blob URL。
> 只写七牛那一种,本地开发时预览会一片空白,而**原因完全看不出来**。
> 另外:签名链接有有效期(30 分钟),所以**每次预览都现取**,不要缓存 URL ——
> 缓存的那份过期后是 403,而学生只会觉得"打不开"。
**顺手修的两个后端问题**:
1. `LocalStorageService.url()` 返回的路径指向一个**不存在的接口**
(我当时建的是 `/{id}/content`,不是 `?path=`)——
现在由 `MaterialFileService` 按 provider 决定:七牛给签名链接、本地给 `/{id}/content`
(按 id 取能复用"这份文件属于你"的归属校验)
2. `/content` 原来固定返回 `application/octet-stream` ——
表现是**点开身份证照片变成下载一个文件**。现在按文件名推 Content-Type + `inline`
### ③ 浏览器实测
- 点「预览」→ 全屏浮层打开、图片**真的解码出来**(`naturalWidth=900`、`complete=true`)
- 直接 fetch 那个签名 URL:**200 / 17728 字节**
- 中间发现一个视觉问题:**浮层的标题与「关闭」被 uni-app 的导航栏盖住了**
(uni-app 的 page-head `z-index` 是 **998**,我给的 95)——
看起来像"标题没渲染出来"。改成 9999 + `padding-top` 让出导航栏高度;
问题清单浮层的 z-index 一并提到 9998
- 修完复验:`headVisible: true`、标题与「关闭」都在顶层可见、图片铺满宽度
### ④ 顺带
`dev-seed-warn-state.py` 改为**没有可用草稿时自己建一份** ——
原来写死 submission id,被 E2E 清理后就报"填报单不存在或不属于你",
**而那看起来像权限问题,不像"数据被清了"**。
### 验收
`e2e-m3-file 32/32`、`m3-submit 31/31`、`m1-storage 16/16`
### 下一步
1. **直传** `POST /mp/files/upload-token`(有了七牛就能按契约实现;
省的是学生那一段上行 —— 几十 MB 的翻拍件不占用我们的带宽与连接)
2. `M1-05` 规则模板
3. 学生端提交前的**图片自检提示**(现在只有校验清单,预览已经能用,可以串起来)
4. 待办(非阻塞):C1 **小程序资质**(已明确会阻塞学生真正开始填报)、
C5 隐私声明法务文案、D1 OCR 引擎选型、D5 一寸照片阈值标定、D6 微信三通道体积上限真机实测