自动头像:本站注册用户保留原头像,只给查不到邮箱的人生成 #8

Closed
opened 2026-10-25 11:55:36 +00:00 by laoyang · 1 comment
Owner

现象

「自动头像」启用后,get_avatar 过滤器会把所有 Gravatar 家族的 URL 都换成本地生成的头像。问题在于:本站在 WordPress 用户表里已注册的用户,即使他们自己有头像(Gravatar 或者站内上传的),也被一并换掉了,看起来就像是全站所有人都成了同一套卡通脸。

用户诉求(原意):邮箱能在本站用户表里查到的,说明这个人是本站用户,就用他自己的头像;查不到的(匿名评论者)才用生成的。

现状代码

inc/theme-avatar.php 的 yang_avatar_filter():

if (!yang_avatar_is_gravatar_url($old_src)) {
    return $avatar;              // 不是 Gravatar 家族 → 放行
}
$email = yang_avatar_email_of($id_or_email);
if ($email === '') {
    return $avatar;              // 没邮箱 → 放行
}
// ↓ 这里之后无条件替换,**完全不看这个邮箱是不是本站用户**
$svg = yang_avatar_svg($email, '', $px);

也就是说:判断只到「是不是 Gravatar 家族」和「有没有邮箱」两层,缺了「这个邮箱属不属于本站注册用户」这一层。

需要做的事

在替换之前加一层判断:邮箱对应本站注册用户时,原样返回 $avatar,不做任何替换。

要处理的边界:

  1. 邮箱大小写、前后空格 —— 查表前统一规范化
  2. 用户已被删除 / 邮箱后来改过 —— 查不到就当匿名,正常生成
  3. 同一邮箱既是注册用户又在评论里出现 —— 一律按注册用户处理(不替换)
  4. 不能拖慢列表页 —— 一页 20 条评论就是 20 次查表,要考虑 WP 的邮箱缓存是否够用;不够的话得批量预取
  5. $id_or_email 本身已经是 WP_User 对象或用户 ID 时,直接判定为注册用户,不必再查表

验收标准

  1. 邮箱属于本站注册用户 → 头像 原样保留($avatar 字符串一字不改)
  2. 邮箱不属于任何用户(匿名评论)→ 正常生成本地头像
  3. 邮箱为空 / 不是 Gravatar 家族 / 已经是 data: URI → 行为不变(放行)
  4. 大小写与空格不同的同一邮箱,结论一致
  5. 用户不存在(已删除)时不报错、不白屏,按匿名处理
  6. 列表页查询次数不得随头像数量线性爆炸(需实测统计,给出数字)

备注

  • 要在后台「启用自动头像」的说明文字里补一句,讲清楚哪些人会换、哪些人不换 —— 否则用户会疑惑「为什么有的人变了有的人没变」。
  • 发布仍走 /theme-update/ 通道,不覆盖服务器主题目录。
## 现象 「自动头像」启用后,`get_avatar` 过滤器会把**所有** Gravatar 家族的 URL 都换成本地生成的头像。问题在于:本站在 WordPress 用户表里**已注册的用户**,即使他们自己有头像(Gravatar 或者站内上传的),也被一并换掉了,看起来就像是全站所有人都成了同一套卡通脸。 用户诉求(原意):**邮箱能在本站用户表里查到的,说明这个人是本站用户,就用他自己的头像;查不到的(匿名评论者)才用生成的。** ## 现状代码 `inc/theme-avatar.php` 的 `yang_avatar_filter()`: ```php if (!yang_avatar_is_gravatar_url($old_src)) { return $avatar; // 不是 Gravatar 家族 → 放行 } $email = yang_avatar_email_of($id_or_email); if ($email === '') { return $avatar; // 没邮箱 → 放行 } // ↓ 这里之后无条件替换,**完全不看这个邮箱是不是本站用户** $svg = yang_avatar_svg($email, '', $px); ``` 也就是说:判断只到「是不是 Gravatar 家族」和「有没有邮箱」两层,缺了「这个邮箱属不属于本站注册用户」这一层。 ## 需要做的事 在替换之前加一层判断:**邮箱对应本站注册用户时,原样返回 `$avatar`,不做任何替换**。 要处理的边界: 1. 邮箱大小写、前后空格 —— 查表前统一规范化 2. 用户已被删除 / 邮箱后来改过 —— 查不到就当匿名,正常生成 3. 同一邮箱既是注册用户又在评论里出现 —— 一律按注册用户处理(不替换) 4. 不能拖慢列表页 —— 一页 20 条评论就是 20 次查表,要考虑 WP 的邮箱缓存是否够用;不够的话得批量预取 5. `$id_or_email` 本身已经是 `WP_User` 对象或用户 ID 时,直接判定为注册用户,不必再查表 ## 验收标准 1. 邮箱属于本站注册用户 → 头像 **原样保留**(`$avatar` 字符串一字不改) 2. 邮箱不属于任何用户(匿名评论)→ 正常生成本地头像 3. 邮箱为空 / 不是 Gravatar 家族 / 已经是 `data:` URI → 行为不变(放行) 4. 大小写与空格不同的同一邮箱,结论一致 5. 用户不存在(已删除)时不报错、不白屏,按匿名处理 6. 列表页查询次数不得随头像数量线性爆炸(需实测统计,给出数字) ## 备注 - 要在后台「启用自动头像」的说明文字里补一句,讲清楚哪些人会换、哪些人不换 —— 否则用户会疑惑「为什么有的人变了有的人没变」。 - 发布仍走 `/theme-update/` 通道,不覆盖服务器主题目录。
Author
Owner

已实现并发布到 3.2.39(代码提交 203721b)。

做了什么

给头像过滤器加了一层「先查本站用户表」的判据,顺序如下:

  1. 头像不是 Gravatar 家族的(本站媒体库上传的、已经是 data: URI 的)→ 一律放行,不碰
  2. 邮箱能在 wp_users 表里查到(get_user_by('email'))→ 原样返回,一个字都不改
  3. 邮箱查不到的匿名访客 → 才生成自动头像
  4. 没有邮箱的(比如系统占位头像)→ 放行

后台新增一个开关「注册用户保留原头像」,默认开启。关掉就退回 3.2.38 的行为(所有人都换)。

有一个服务端无法回避的限制,需要你知道

你选的是「注册用户自己也没真头像时,给他生成」。但这一条在服务端做不到,原因:

  • WordPress 的 get_avatar() 只负责拼 URL 交给浏览器,它从不检查图上到底有没有内容
  • Gravatar 对「没上传过头像的邮箱」也会返回一张图 —— 系统默认的灰色人像
  • 要分辨「这张图是真照片还是默认灰人像」只有一条路:真的去 gravatar.com 把图拉下来比对

而这个功能从立项起就定了零外网调用的硬约束(发布脚本会断言包里不含 curl / wp_remote_get / fsockopen / file_get_contents,防止有人偷偷联网)。

所以最终做法是:只要是注册用户就不动。代价是「注册了但没上传过头像」的人,头像仍然是那张灰人像,不会被换成卡通脸。这个取舍已经写进后台开关的说明文字里。

如果你宁可打破零外网约束也要给这类人生成,那需要单独开工单 —— 那会带来隐私(给第三方发送访客邮箱)和性能(每个头像一次外网请求)两个新问题,不是小改动。

验证

  • 注册用户原样返回 / 匿名访客生成 / 已删除用户按匿名处理 / 开关关闭退回旧行为 / 放行条件未被破坏 / WP_User 与 WP_Comment 对象入参 —— 19 项全通过
  • 查表性能:20 个不同邮箱 = 20 次查表;同一邮箱重复 20 次 = 1 次(加了请求内缓存,评论列表一页 20 条里同一个人常有 2~3 条)
  • 上述判据反向注入 8 项,全部按预期变红
  • 包内容契约 52 项、包内断言反向 20 项全绿

发布

  • update.json 已指向 3.2.39,zip 与 readme 均 200
  • 服务器上的主题目录没有被覆盖,仍是 3.2.38 —— 需要你在 WP 后台点一次升级才会装上 3.2.39
已实现并发布到 3.2.39(代码提交 203721b)。 ## 做了什么 给头像过滤器加了一层「先查本站用户表」的判据,顺序如下: 1. 头像不是 Gravatar 家族的(本站媒体库上传的、已经是 data: URI 的)→ 一律放行,不碰 2. 邮箱能在 wp_users 表里查到(`get_user_by('email')`)→ **原样返回,一个字都不改** 3. 邮箱查不到的匿名访客 → 才生成自动头像 4. 没有邮箱的(比如系统占位头像)→ 放行 后台新增一个开关「注册用户保留原头像」,默认开启。关掉就退回 3.2.38 的行为(所有人都换)。 ## 有一个服务端无法回避的限制,需要你知道 你选的是「注册用户自己也没真头像时,给他生成」。但这一条**在服务端做不到**,原因: - WordPress 的 `get_avatar()` 只负责拼 URL 交给浏览器,**它从不检查图上到底有没有内容** - Gravatar 对「没上传过头像的邮箱」也会返回一张图 —— 系统默认的灰色人像 - 要分辨「这张图是真照片还是默认灰人像」只有一条路:**真的去 gravatar.com 把图拉下来比对** 而这个功能从立项起就定了**零外网调用**的硬约束(发布脚本会断言包里不含 curl / wp_remote_get / fsockopen / file_get_contents,防止有人偷偷联网)。 所以最终做法是:**只要是注册用户就不动**。代价是「注册了但没上传过头像」的人,头像仍然是那张灰人像,不会被换成卡通脸。这个取舍已经写进后台开关的说明文字里。 如果你宁可打破零外网约束也要给这类人生成,那需要单独开工单 —— 那会带来隐私(给第三方发送访客邮箱)和性能(每个头像一次外网请求)两个新问题,不是小改动。 ## 验证 - 注册用户原样返回 / 匿名访客生成 / 已删除用户按匿名处理 / 开关关闭退回旧行为 / 放行条件未被破坏 / WP_User 与 WP_Comment 对象入参 —— **19 项全通过** - 查表性能:20 个不同邮箱 = 20 次查表;**同一邮箱重复 20 次 = 1 次**(加了请求内缓存,评论列表一页 20 条里同一个人常有 2~3 条) - 上述判据反向注入 8 项,全部按预期变红 - 包内容契约 52 项、包内断言反向 20 项全绿 ## 发布 - `update.json` 已指向 3.2.39,zip 与 readme 均 200 - **服务器上的主题目录没有被覆盖**,仍是 3.2.38 —— 需要你在 WP 后台点一次升级才会装上 3.2.39
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: laoyang/wordpress-kratos-yang#8