[开发]昵称插件开发方案(用户自定义显示名)

👑Lv.11 元老 🌞 骨干会员
2026-10-25 01:34:53

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/昵称插件开发方案_2026-09-27.md。

昵称插件开发方案(用户自定义显示名)

日期:2026-09-27 | 依据:docs/插件开发规范.md

状态:待确认(本文只出方案,不动手写代码)

建议插件名 / 目录名:nickname(plugins/nickname 空闲 ✓;无下划线 → 不需要 class_alias)

本文所有结论均来自实测,文件:行号 可逐条复核。


一、结论摘要(先看这段)

一句话:「昵称」只能做成显示名,不能做成「改用户名」(登录只认用户名,见 §2.3);

而「全站把用户名换成昵称」在 FlintHub 现有架构里没有单一渲染入口(71 处 / 24 文件 / 17 种数据形状,见 §2.4),

所以纯插件路线只能覆盖约 85%,且会在插件渲染的片段里出现「一处昵称、一处用户名」的不一致。

三条路线取舍:

路线核心改动覆盖率风险建议
A 纯插件(视图数据改写)0~85%中:语义误伤 + 片段不一致可选(想零核心改动时)
B 核心加扩展点 + 插件1 列 + 模型 + ~14 前台视图~95%低:语义清晰推荐
C 昵称作为附加显示0仅 3~4 处极低不满足「全站显示昵称」

推荐 B 线,两个理由:

  1. 本项目已有「给核心加扩展点再写插件」的既定实践 —— thread_show_top(2026-09-25 新增)、

right_sidebar_user_actions_above(2026-09-27 新增)都是这么加的(docs/插件开发规范.md §9.1 F/G 组);

  1. ensureColumnPatches()(app/SplitDB/Schema.php:611)就是为「给存量库补列」设计的,加列成本极低且幂等。

二、现状核查(实测,非推断)

2.1 users 表没有昵称列

实测 25 列:`id, username, password, email, avatar, signature, role, status, group_id, post_count,

points, level, theme, remember_token, last_active_at, last_login, email_verified, email_verify_token,

email_verify_token_sent_at, created_at, hide_right_sidebar, night_theme_auto, list_excerpt,

list_excerpt_len, home_mode —— 无 nickname`。

  • 新库建表:app/SplitDB/Schema.php:270-293
  • 存量库补列入口:app/SplitDB/Schema.php:631-639('users' => [...] 补丁块,哈希 marker 门控)

2.2 User 模型没有写用户名的出口

项实测
$fillable['last_login'](app/Models/User.php:18)→ update() 只认 last_login(§11.8 第 4 条)
已有写方法updateProfile($id,$email,$signature)(:145)、updateAvatar、updateByAdmin(:518)
缺失无 updateUsername() / updateNickname()

2.3 登录名 = 用户名 → 「改用户名」这条路是死的

app/Controllers/AuthController.php:89:$user = $userModel->findByUsername($username);

只按用户名登录,无邮箱登录入口。

→ 改 username 等于改登录凭据(老用户直接登不上),与「昵称」语义冲突 → 本方案排除该做法。

2.4 全站 71 处用户名渲染点,没有单一入口

项实测值
渲染点71 处 / 24 个文件(app/Views/)
数据形状17 种:$thread / $post / $item / $user / $currentUser / $blog / $u / $t / $log / $comment / $tei / $b / $r / $lead / $detailItem / $message['sender_username'] / $msg['receiver_username']
输出方式全部 $this->e(...) → TemplateCompiler::e()(app/Core/TemplateCompiler.php:298)—— 纯转义器,无过滤点
派生用法头像首字 mb_substr($username, 0, 1)(10 处)、data-quote-user 属性(2 处)、highlightSearchTerms()(1 处)

典型样例:

app/Views/forum/index.php:47    $username = $this->e($thread['username'] ?? '');
app/Views/thread/show.php:281   <?php echo $this->e($post['username'] ?? ''); ?>
app/Views/admin/logs/audit.php:62  <?php echo $this->e((string)($log['username'] ?? '')) ?>
app/Views/profile/index.php:52  

2.5 唯一能改视图数据的钩子 controller_view_before 有 3 个硬约束

触发点 app/Core/Controller.php:49:

try {
    \app\Helpers\Plugin::hook('controller_view_before', ['template' => $template, 'data' => &$data]);
} catch (\Throwable $e) { ... }

$data = \array_merge($defaults, $data);   // ← :54,钩子跑在它「之前」

  1. 时序:钩子触发在 array_merge($defaults, $data)(:54)之前 → 钩子执行时 $data 里

还没有 currentUser / currentUsername(它们在 $defaults,:34-35)。

  1. 范围:只对走 view() 的页面生效。
  2. viewRaw() 绕过它(:61,直接 display(),不触发任何钩子)。实测绕过点:
  • plugins/floor_reply/FrontController.php:216,249 → 楼中楼片段,视图里就显示作者名(views/list.php:45,49)
  • plugins/daily_checkin/CheckinController.php:203,274,291
  • plugins/mod_system/FrontController.php:105,168

2.6 已有的「昵称」先例都与本需求无关

位置性质
ai_assistant_members.nicknameAI 人设名(后台展示用)
third_party_login 存第三方 nick第三方账号原始昵称

→ 没有可复用的昵称机制;plugins/nickname 目录名空闲。


三、三条路线对比(详细)

A 线 · 纯插件:controller_view_before 深度改写视图数据

做法:钩子里按 $template 白名单,递归遍历 $data,凡是同时含 user_id 与 username 的行,

把 username 换成插件库里的昵称;先把所有 user_id 收集齐,再一次 WHERE user_id IN (...) 查回(1 次查询)。

✅ 优点❌ 缺点
零核心改动,可立即交付盲区 1:viewRaw 片段改不到 → 详情页显示昵称、楼中楼显示用户名(§2.5-3)
能覆盖 forum/category、forum/index、home/、thread/show、blog/、tags/show、message/*盲区 2:其它插件渲染的名字(leaderboard / new_members / points_mall…)全部改不到
不动数据库结构语义误伤:profile/index.php:52、admin/user_edit.php:21、admin/users.php:37 的 disabled 输入框是「登录名」语义,admin/logs/audit.php:62 是审计留痕 → 换成昵称就是假信息(只能靠模板白名单排除,属「打补丁」)
search/index.php:123 的 highlightSearchTerms($username) 会失效
每页多一次递归遍历($data 可能很大),需深度上限 + 键白名单

结论:能跑,但脆。适合「只想尽快有个能用的版本」且接受上述不一致。

B 线 · 核心加扩展点 + 插件(推荐)

做法:

  1. 核心 users 加 nickname TEXT DEFAULT NULL(新库进建表语句,老库进 ensureColumnPatches);
  2. 核心 User 加 setNickname(int $id, ?string $nick) / displayName(array $user): string(nickname ?: username)

/ batchGetDisplayNames(array $ids);

  1. 核心 TemplateCompiler 加视图助手 name($row)(内部走 User::displayName),前台视图分批迁移;
  2. 插件 nickname 负责:设置入口、校验、审计、后台管理。
✅ 优点❌ 代价
语义干净:username 永远是登录名与 @提及 锚点,只改显示要动核心(1 处建表 + 1 处补列 + 模型 + ~14 个前台视图)
admin / audit / search 不受影响(后台视图不迁移,继续显示 username)插件渲染的名字(floor_reply 等)仍需各自跟进
与项目既定实践一致(近期两次核心加钩子)

⚠️ 关键差别:B 线的「未覆盖」是显式、可控的(后台就是不换、插件要自己跟进);

A 线的「未覆盖」是静默、随机的(同一页面上半截昵称下半截用户名)。

C 线 · 昵称作为「附加显示」

只在 nav_user_menu_items(#10,双触发)、right_sidebar_user_actions_above(#42,仅登录态)、

profile_user_card_after(#25)显示昵称(用户下拉 / 右栏用户卡 / 个人主页卡)。

  • ✅ 零核心改动、零风险、无「片段不一致」问题(因为不替换)
  • ❌ 不满足「全站用昵称代替用户名」—— 列表页、帖子页、回复仍是用户名

四、推荐方案(B 线)技术设计

4.1 数据落点

项决策理由
存储位置核心 users.nickname 列(不建独立库)昵称与用户 1:1;独立库会让列表页(每页 N 个作者)退化成跨库 JOIN —— users 在 data/meta/business.sqlite(MEMORY §10 红线),跨库代价高
空值语义NULL = 未设置 → 显示回退 username零迁移成本,老用户天然「没昵称」
唯一性待拍板(§五·3)允许重名时 @提及 仍走 username,不受影响

4.2 核心改动清单(精确落点)

#文件位置改动
1app/SplitDB/Schema.php:270-293 建表语句加一行 nickname TEXT DEFAULT NULL,
2app/SplitDB/Schema.php:631-639 'users' 补丁块加 'nickname' => 'TEXT DEFAULT NULL'(清单哈希变 → 老库自动补列)
3app/Models/User.php新增方法(batchGetNames() :351 之后)setNickname() / displayName() / batchGetDisplayNames()
4app/Core/TemplateCompiler.php:298 e() 附近新增 name(array $row): string 视图助手
5前台视图 ~14 文件见 §4.3$this->e($x['username']) → $this->name($x)

⚠️ 第 2 条的机制:ensureColumnPatches() 的 marker 是

md5(json_encode($columnPatches) . json_encode($indexPatches) . $tagsUniqueDdl)(Schema.php:667)

→ 改清单 → 哈希变 → 自动重跑,不需要手删旧 .ok。

(MEMORY §3 那条「改完手删旧 .ok」指的是只改 ALTER 语句却不改清单的情形 —— 哈希不变会静默跳过。见 §七·7)

4.3 视图迁移范围(分批,后台不动)

P1 迁移(前台 14 文件)

forum/index、forum/category、home/index_default、home/index_cms、thread/show、

blog/index、blog/detail、tags/show、search/index、message/inbox、message/sent、

message/view、_components/right_sidebar、profile/index

⚠️ profile/index.php 只改 :30 的显示名;:52 那个 disabled 输入框是「登录名」语义,保持 username 不动。

P2 不迁移(后台 7 文件)

admin/index、admin/users、admin/user_edit、admin/threads、admin/trashed、

admin/blog_manage、admin/logs/audit —— 后台一律按「登录名」语义保留 username。

4.4 插件职责与文件清单

plugins/nickname/
├── plugin.json                 # name=nickname, version=1.0.0, admin_url
├── Plugin.php                  # 生命周期(无下划线 → 不需要 class_alias)
├── FrontController.php         # 设置页 GET/POST(PRG 模式)、校验、写审计
├── AdminController.php         # 后台:列表 / 搜索 / 强制重置 / 敏感词配置
├── hook/
│   ├── nav_user_menu_items.php               # #10 双触发(pc/mobile)→「设置昵称」
│   └── right_sidebar_user_actions_above.php  # #42 登录态 →「设置昵称」快捷入口
├── views/
│   ├── setting.php             # 前台设置表单(走 view(),含 CSRF)
│   ├── admin_list.php          # 后台列表(.admin-table + 双类提权,MEMORY §5)
│   └── _feedback.php           # htmx 局部回包(走 viewRaw,⚠️ 不带 /

4.5 校验规则(初版,待拍板)

项建议值依据
长度2–16 字符(mb_strlen)右栏用户卡 ≈ 5 中文字宽(§11.26);列表 .mn-row-author 会截断
字符集中英文、数字、下划线、连字符;禁空格与 @提及 正则 [\p{L}\p{N}_\-]+ 对齐(plugins/floor_reply/Plugin.php renderMentions)
保留字禁 admin / 管理员 / 站点名 / 已存在的 username防冒充
敏感词复用 content_review 插件词库,或插件自带词表见 §五·4
修改频率每 N 天一次(插件表记 updated_at)防刷;N 见 §五·5
唯一性待拍板见 §五·3

4.6 i18n 键(初版,~12 键 × 3 语)

plugin.nickname.title / .setting / .current / .new / .hint / .save / .saved /

.err_len / .err_charset / .err_reserved / .err_freq / .admin_title


五、待你拍板的 6 个点

  1. 目标确认:昵称 = 全站显示名,username 仍是登录名 —— 这个理解对吗?

(如果你的意思是「允许用户改用户名」,那要另开方案:登录凭据会变,且模型无出口,见 §2.2/§2.3)

  1. 路线:A(纯插件 / 85% 覆盖 / 零核心改动)| B(核心扩展点 / 推荐) | C(附加显示 / 不满足需求)
  2. 唯一性:昵称允许重名吗?(建议:允许。@提及 走 username,两人同名不影响功能)
  3. 敏感词:复用 content_review 插件的词库,还是插件自带一份?
  4. 修改频率:限制「每 N 天一次」还是不限?N = ?
  5. 入口:用户下拉(#10)+ 右栏(#42)都要,还是只留一个?

(⚠️ 右栏 ≤1024px 会 display:none(§11.26),所以 #10 必须有)


六、实施阶段(每阶段末暂停确认)

阶段内容产出
P0侦察复核(本方案 §二)✅ 已完成
P1核心扩展点:Schema 加列 + User 模型方法 + TemplateCompiler::name() + 只迁移 thread/show 做样板跑通后停
P2其余 13 个前台视图迁移停
P3插件骨架:plugin.json / Plugin.php / 设置页 / 校验停
P4入口与后台:nav_user_menu_items + right_sidebar_user_actions_above + 后台管理页停
P5验证:三语键比对 + plugin_hygiene.py + HTTP 冒烟 + 双主题走查 + 数据洁净度交付


七、风险与坑(逐条对应规范)

  1. viewRaw 绕过钩子(§2.5-3)→ B 线靠视图迁移,不受影响;A 线是硬伤。
  2. 钩子在 defaults merge 之前(Controller.php:49 vs :54)→ A 线必须自己

Auth::getCurrentUser() 后注入完整 currentUser,否则会清掉头像/积分(array_merge 顶层覆盖)。

  1. 头像首字 mb_substr($username, 0, 1)(10 处) → 迁移时要一起换成昵称首字,否则头像字与显示名不一致。
  2. data-quote-user(thread/show.php:87,266) → 消费方 assets/js/editor.js:2436-2439 把它当引用文本塞进编辑器;

而 @提及 是按 username 反查的 → 若换成昵称,引用产生的 @昵称 无法被解析。

建议保留 data-quote-user = username(待拍板 1 的延伸)。

  1. @提及 与昵称的关系 → floor_reply 的 nameMap 是「username → uid」;

昵称不参与提及,否则两套名字会打架。

  1. 插件零内联 + 闭包作用域(§11.24)→ 钩子文件里 $this 不可用,

只能 htmlspecialchars($s, ENT_QUOTES, 'UTF-8') + Csrf::token()。

  1. ensureColumnPatches 的 marker 是哈希门控(Schema.php:667)→ 加列时必须把列写进 $columnPatches 清单;

只改 ALTER 语句而清单不变 → 哈希不变 → 静默不执行。

  1. 缓存:B 线改的是视图文件 → 上线后必须走后台「维护→清理缓存」(data/runtime/pages/*.html 永久缓存)。
  2. users 在 data/meta/business.sqlite(MEMORY §10)→ 探针连库别连错。
  3. 两套等级体系 / 白名单同步(MEMORY §8)→ 若昵称要显示成徽章,.user-level-badge 那两处白名单要同步。
  4. 后台表格列序硬编码(MEMORY §5)→ 插件后台列表若含行内表单,须 .pp-table.pp-table td.pp-col-x 双类提权。

八、待确认清单

  • [ ] §五 的 6 个待拍板点
  • [ ] 是否接受「动核心」(1 处建表 + 1 处补列 + 模型 + ~14 个前台视图)
  • [ ] 插件名用 nickname 还是别的
  • [ ] 是否要「改名审计 / 昵称历史」(后台可查谁改过什么)
  • [ ] 「昵称唯一」与「敏感词」是否 v1.0.0 就做,还是留 v1.1.0
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 4 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录