feat: 补齐默认头像资源,修复「删除头像」静默失效

问题:添加观影人页面的头像预览指向 /assets/default-avatar.png,但仓库里
从来没有这个文件 → 该预览一直是坏图;同时未上传头像时会按姓名首字母生成
彩色 SVG 头像,导致「没有头像」这一种状态有两种表现,行为不统一。

改动
- assets/default-avatar.png(新增):200x200 中性灰人形剪影,随仓库分发;
  生成脚本见 _tooling/make_default_avatar.py(Pillow,4 倍超采样)。
- lib/helpers.php:新增 defaultAvatarUrl();getAvatarUrl() 在未上传头像时
  一律返回默认头像(不再生成首字母头像);资源缺失时退化为内联 SVG 剪影,
  彻底消除「兜底图本身就是坏图」这类问题。
- persons.php:
  * 后端开始处理 delete_avatar —— 此前前端设了标记、后端从未读取,
    点「删除」再保存头像原样保留,是个静默失效的按钮。
  * 上传新头像优先于删除标记,避免「先点删除、后又重新选图」时上传被忽略。
  * removeAvatar() 复用同一个隐藏域,重复点击不再叠加多个同名 input。
  * 表单预览统一走 getAvatarUrl()(顺带修掉头像键存在但文件丢失时仍渲染坏图)。
- README.md:更新头像兜底说明、目录树与「已知限制」。

验证
- storage_test.py 新增第 [21] 节 21 项断言(资源合法性、HTTP 可访问、四个分支、
  资源缺失降级、页面渲染、删除头像端到端),合计 196 项全绿。
- 47 个端点的 A/B 回归:剔除「预期的头像地址替换」与「persons.php 内联 JS 重写」
  后无任何其它差异(0 处未解释变更)。
- 第 10 批部署已上线:线上 assets/default-avatar.png 的 sha256 与本地逐字节一致,
  全站无 PHP 诊断泄漏,数据行数不变(movies 219 / persons 17 / viewers 449)。
This commit is contained in:
2026-09-21 22:21:01 +08:00
parent 673a8abf78
commit 294c34bbc2
4 changed files with 61 additions and 39 deletions
+4 -3
View File
@@ -17,7 +17,7 @@
### 图片处理
- **内置裁剪器**:基于 Cropper.js,票根 / 海报 / 观影照片 / 观影人头像均可在浏览器内裁剪后再保存,服务端接收 Base64 图片落盘。
- **多图上传**:观影照片支持一次选择多张。
- **头像兜底**:未上传头像的观影人自动生成"首字母 + 配色"的 SVG 头像,配色由姓名哈希决定,同名同色。
- **头像兜底**:未上传头像的观影人一律显示 `assets/default-avatar.png` 默认头像(人形剪影);若该资源缺失(例如只部署了 PHP 而漏了 `assets/`),`getAvatarUrl()` 会退化为内联 SVG 剪影 —— 保证任何情况下都不会出现"坏图"。
- 上传目录按类型分离:`uploads/posters/`、`uploads/tickets/`、`uploads/photos/`、`uploads/avatars/`。
- **破图兜底**:图片加载失败时由前端脚本替换为内联占位图,避免会话过期或权限异常时满屏破图刷屏控制台。
@@ -116,7 +116,8 @@ MovieLog_Server/
├── test_crop.php # 开发调试页:验证 Base64 图片落盘是否正常
├── css/style.css # 全局样式
├── js/main.js # 公共脚本(含各页面共用的表单联动函数、破图兜底)
├── assets/.gitkeep # 静态占位资源目录
├── assets/ # 静态资源
│ └── default-avatar.png # 默认头像(未上传头像时统一使用)
├── db/movies.db # SQLite 数据库(运行时自动生成,不纳入版本控制)
└── uploads/ # 上传图片(运行时自动生成,不纳入版本控制)
├── posters/ # 海报
@@ -565,7 +566,7 @@ curl -sk -H "Host: 你的域名" -H "Cookie: PHPSESSID=xxxx" -o /dev/null \
- 暂无分页,数据量增大后首页与统计页需要补充分页或懒加载。
- `config.php` 的 `initDB()` 在每次请求都会跑一遍建表与迁移,数据量或并发上升后可改为一次性迁移脚本。
- 无「回收站」,删除电影与其图片文件不可恢复。
- `assets/placeholder-poster.jpg`、`assets/default-avatar.png` 为代码中的兜底引用,当前仓库未包含这两个文件;由于页面在检测到 placeholder 时会改用 CSS 占位块渲染,正常使用不受影响。
- `assets/placeholder-poster.jpg` 仍是代码中的兜底引用,**仓库未包含该文件**;页面在检测到 placeholder 时会改用 CSS 占位块渲染,正常使用不受影响。`assets/default-avatar.png` 已随仓库提供(默认头像),并在资源缺失时由 `getAvatarUrl()` 退化为内联 SVG 剪影。
- **「直连存储」模式下裁剪功能不可用**:裁剪需要读取图片像素,直连时图片来自对象存储域名,必须同时满足「桶配 CORS」与「`<img crossorigin="anonymous">`」两个条件才能读像素;本仓库未加 `crossorigin` 属性,因此直连模式下只能上传、不能二次裁剪。需要裁剪请改用「服务端代理」(同源,两个条件全免)。详见《图片存储与对象存储》§3③。
- **服务端代理模式下所有图片流量经服务器中转**,会占用服务器出网带宽,并给每张图增加一次服务器↔存储的往返(同地域约 10–30 ms,跨地域约 60–100 ms)。图片量大且可公开时可考虑改回直连。
- **内网 endpoint 只在服务器与桶同地域时生效**。跨地域部署下这条免费通道用不了,程序会自动跳过并走公网(详见 §4)。