xnx_oauth 插件 - 第三方登录账号绑定流程重构
问题背景
原逻辑中,未绑定的第三方账号登录时会自动创建新 Xiuno 账号(auto_register 开关),导致:
用户已有本站账号,用 QQ 登录会创建独立新账号,无法合并
多平台登录(QQ、微信、GitHub)各自创建独立账号,无法统一
改动方案
移除 auto_register 机制,改为「绑定选择页」流程:第三方授权后未绑定时,引导用户选择「登录已有账号绑定」或「注册新账号绑定」。
修改文件
核心逻辑层:
plugin/xnx_oauth/model/OAuthService.php移除
createUserFromOAuth()方法loginOrBind()第 4-5 步(auto_register / reject)合并为:存 session + 返回pending_bind新增
storePendingBind()/getPendingBind()/clearPendingBind()/bindFromPending()四个方法session 待绑定信息 10 分钟过期
plugin/xnx_oauth/route/oauth.phpcallback 成功后检测
action=pending_bind,跳转到oauth-bind-selectcase 'bind'分支新增两个子路由:oauth-bind-select:渲染绑定选择页(未登录时)oauth-bind-finalize:已登录时从 session 取待绑定信息完成绑定
模板层:
plugin/xnx_oauth/view/htm/oauth_bind_select.htm(新增)显示第三方账号信息(头像、昵称、平台)
两个选择按钮:登录已有账号 / 注册新账号
跳转登录/注册页时带
referer=oauth-bind-finalize参数
Hook 层:
plugin/xnx_oauth/hook/user_login_post_end.php(新增)用户登录成功后检测 session 待绑定,自动调用
bindFromPending()hook 内
message()+exit跳过宿主默认跳转
plugin/xnx_oauth/hook/user_create_post_end.php(新增)用户注册成功后检测 session 待绑定,自动调用
bindFromPending()hook 内
message()+exit跳过宿主默认跳转
plugin/xnx_oauth/hook/user_login_submit_after.htm(修改)检测 session 待绑定时,在登录表单上方显示提示条
plugin/xnx_oauth/hook/user_create_submit_after.htm(修改)检测 session 待绑定时,在注册表单上方显示提示条
设置层:
plugin/xnx_oauth/setting.php(修改)移除
auto_register字段的读取和保存
plugin/xnx_oauth/admin/view/htm/oauth_setting.htm(修改)移除 auto_register 开关 UI
语言包:
plugin/xnx_oauth/hook/lang_zh_cn_bbs.php/lang_zh_tw_bbs.php/lang_en_us_bbs.php移除
oauth_admin_auto_register/oauth_admin_auto_register_tip新增绑定选择页相关 13 个 lang key
数据流
用户点 QQ 登录 → OAuth 授权 → 回调
├─ openid 已绑定 → 直接登录(不变)
├─ 未绑定但已登录 → 绑定到当前用户(不变)
├─ 未登录但邮箱匹配且开启 auto_merge → 自动合并(不变)
└─ 其他 → 存 session → 跳「绑定选择页」
├─ 登录已有账号 → 登录成功 hook 自动绑定
└─ 注册新账号 → 注册成功 hook 自动绑定
注意事项
历史已通过 auto_register 创建的碎片账号无法自动合并,需要后续单独做「账号合并」功能
session 待绑定信息 10 分钟过期,超时需重新发起第三方登录
已清理 tmp 编译缓存(route_oauth / model_OAuthService / view_htm_* / route_user / lang_*)
xnx_gitee_projects 插件(新增)- Gitee 项目展示
功能概述
独立扩展插件,让已绑定 Gitee 的用户可以选择要展示的公开项目,项目展示在 4 个位置:
my 个人主页侧栏菜单 → 独立项目页面
user 公开主页 tab → 项目列表
帖子详情页右侧栏作者卡片 → 项目摘要(最多 3 个)
独立页面
gitee-projects-{uid}
技术方案
使用 Gitee 公开 API
/users/{username}/repos,不需要 access_token从
xnx_oauth_bind表的raw_data字段解析 Gitee 用户名(login)24 小时缓存 + 手动强制刷新
用户最多选择 10 个项目展示
新增文件
插件结构:
plugin/xnx_gitee_projects/
├── conf.json -- 插件配置(依赖 xnx_oauth)
├── install.php -- 建表 xnx_gitee_projects
├── uninstall.php -- 删表
├── model/GiteeProjectsService.php -- 核心 Service(API 调用、缓存、选择管理)
├── route/gitee.php -- 路由控制器
├── hook/
│ ├── index_route_case_end.php -- 注册路由 case 'gitee'
│ ├── model_route_table_end.php -- 注册路由模板
│ ├── model_inc_file.php -- 注册 Service 类加载
│ ├── my_sidebar_nav_threads_before.htm -- my 侧栏菜单(PC)
│ ├── my_sidebar_nav_threads_before_mobile.htm -- my 侧栏菜单(移动端)
│ ├── user_nav_end.htm -- user 公开主页 tab
│ ├── thread_user_after.htm -- 帖子作者卡片项目摘要
│ ├── lang_zh_cn_bbs.php -- 简体中文语言包
│ ├── lang_zh_tw_bbs.php -- 繁体中文语言包
│ └── lang_en_us_bbs.php -- 英文语言包
└── view/htm/
├── gitee_projects.htm -- 独立页面
├── gitee_projects_settings.htm -- 设置页(选择项目)
└── gitee_projects_tab.htm -- htmx 片段(tab 内容)
数据表
bbs_xnx_gitee_projects:
id, uid, gitee_username, repos_cache (JSON), selected_repo_ids (JSON), last_sync_time, last_sync_ip
路由
gitee-projects-{uid}- 独立页面gitee-projects-settings- 设置页gitee-projects-sync- 手动刷新(POST)gitee-projects-save- 保存选择(POST)gitee-projects-tab-{uid}- htmx 片段端点
使用前提
已安装并启用 xnx_oauth 插件
用户已绑定 Gitee 账号
需在后台插件管理中安装本插件
注意事项
Gitee API
/users/{username}/repos只返回公开项目,私有项目不展示项目列表缓存 24 小时,用户可在设置页手动强制刷新
代理配置复用 xnx_oauth 的代理设置(proxy_host/proxy_port 等)
已清理 tmp 编译缓存
错误日志修复 - 3 个线上报错
1. xnx_oauth - finfo_close() PHP 8.5 弃用警告
问题:plugin/xnx_oauth/model/OAuthService.php 第 871 行调用 finfo_close($finfo),PHP 8.5+ 已弃用此函数(finfo 对象会自动释放),触发 [8192] Function finfo_close() is deprecated 警告,出现在 Gitee 回调下载头像流程。
修复:删除 finfo_close($finfo); 调用,添加注释说明 PHP 8.5+ 自动释放。
文件:plugin/xnx_oauth/model/OAuthService.php
2. xnx_appcenter - bumpListVersion() 私有方法外部调用错误
问题:plugin/xnx_appcenter/model/AppCenterService.php 第 1749 行 bumpListVersion() 定义为 private static,但 setting.php 第 297 行(管理员审核应用 slug 修改后)从全局作用域调用 AppCenterService::bumpListVersion(),触发 Call to private method ... from global scope fatal error。
修复:将 private static function bumpListVersion() 改为 public static function bumpListVersion()。
文件:plugin/xnx_appcenter/model/AppCenterService.php
3. xnx_landing - user_find_by_uids 传数组 TypeError(历史日志,已修复)
问题:2026-07-27 错误日志显示 plugin/xnx_landing/route/landing.php 第 52 行调用 user_find_by_uids() 传入数组触发 trim(): Argument #1 ($string) must be of type string, array given。
状态:源码已在之前修复(第 53 行用 implode(',', $uids) 转为逗号分隔字符串),07-27 日志为历史错误。本次确认无需再改。
部署注意
线上部署后需清理 tmp 编译缓存:
rm -f tmp/plugin_xnx_oauth_model_OAuthService.php tmp/plugin_xnx_appcenter_model_AppCenterService.php
插件依赖格式修复 - xnx_gitee_projects 启用报错
问题背景
xnx_gitee_projects/conf.json 的 dependencies 字段使用了索引数组格式 ["xnx_oauth"],但插件系统 plugin_dependencies() 函数(model/plugin.func.php:209)按关联数组 $_dir => $version 遍历。索引数组传入后 $_dir 变成数字 0(而非 "xnx_oauth"),导致 isset($plugins[0]) 为 false,依赖检查永远失败,启用时报"缺少依赖"错误。
改动方案
修插件:将
conf.json的 dependencies 从索引数组改为推荐的 object 格式增强系统:在
plugin_dependencies()中增加格式规范化逻辑,同时兼容两种写法
修改文件
plugin/xnx_gitee_projects/conf.jsondependencies从["xnx_oauth"]改为{"xnx_oauth": "*"}
model/plugin.func.php(plugin_dependencies()函数)新增格式规范化逻辑:遍历 dependencies 时检测 key 是否为 int,索引数组的 value 当作插件目录名、版本约束视为
*;关联数组原样保留
docs/plugindev/02-plugin-structure.mddependencies 字段类型从
object改为object | array,补充两种格式说明和版本约束语义
docs/plugindev/01-architecture.md依赖章节补充兼容格式示例和规范化逻辑说明
docs/xiunox-plugin-dev/SKILL.mdconf.json 实现步骤补充 dependencies 格式说明
部署注意
清理 tmp 编译缓存:
rm -f tmp/model_plugin.func.php
xnx_gitee_projects 路由修复 + URL 参数分隔符文档预防
问题背景
xnx_gitee_projects/route/gitee.php 的路由 case 值含 -(如 case 'projects-settings'),但 Xiuno URL 按 - 分隔参数,gitee-projects-settings 被解析为 param(1)='projects'、param(2)='settings'——param(1) 只取单段,永远匹配不到 'projects-settings',导致请求落入默认分支报"参数错误"。
改动方案
修插件:重写路由逻辑,用
param(1)取主动作 +param(2)取子动作的嵌套模式文档预防:在 4 处文档中明确记录"路由 case 值禁止含
-"约定,防止后续插件重复踩坑
修改文件
插件层:
plugin/xnx_gitee_projects/route/gitee.php重写为
param(1)='projects'+param(2)子动作/uid 嵌套模式is_numeric(param(2))区分独立页面(uid)和子动作(settings/sync/save/tab)projects-tab子动作的 uid 从param(3)取
文档层:
docs/plugindev/04-api-cheatsheet.md— param() 说明后新增 ⚠️ 框,含错误/正确代码对比docs/xiunox-plugin-dev/references/api-cheatsheet.md— 精简版同步补充docs/xiunox-plugin-dev/SKILL.md— 硬规则表新增"路由 case 值"行 + 失败策略表新增"路由报参数错误"行.trae/rules/bugfix_rules.md— 新增第 15 节,含根因/违规示例/正确写法/自检命令project_memory.md— 沉淀路由 case 值禁止含-规则
部署注意
路由文件通过普通 include 加载(非 _include()),修改后直接生效,无需清 tmp。
布局与 Card 规范统一 - x-card + 三栏骨架
问题背景
部分插件模板(如 xnx_gitee_projects)使用 <div class="container"><div class="row"><div class="col-lg-8"> 硬编码布局,未使用系统三栏骨架 layout_three_column.inc.htm,导致 htmx 局部刷新失效风险、与系统页面视觉不一致。同时需要强化 x-card + card 组合、禁止 border 的规范。
改动方案
文档规范:在 4 处文档中新增布局与 Card 规范章节
插件修复:将 xnx_gitee_projects 两个模板改为三栏骨架
修改文件
文档层:
docs/plugindev/05-frontend-security.md— 新增第 9 节「布局与 Card 规范」,含三栏骨架用法、骨架变量表、x-card 规范、border 禁用场景;小结补充布局和 Card 条目docs/xiunox-plugin-dev/references/frontend-patterns.md— 新增第 10 节「布局与 Card 规范」,含完整代码示例和错误/正确对比docs/xiunox-plugin-dev/SKILL.md— 硬规则表 Card 行强化为「必须 x-card + card 组合」+ 新增「前台布局」行;交付检查表拆分为 Card 和布局两条user_profile.md— 沉淀 Card UI 和页面布局偏好
插件层:
plugin/xnx_gitee_projects/view/htm/gitee_projects.htm从
container > row > col-lg-8硬编码改为ob_start+$main_content+layout_three_column.inc.htm三栏骨架设
$sidebar_left_file=''、$sidebar_right_file=''只显示中栏
plugin/xnx_gitee_projects/view/htm/gitee_projects_settings.htm同上改为三栏骨架
JS 脚本移至骨架 include 之后
核心规范
Card 必须
x-card+card组合,禁止裸用card或border/border-*前台页面必须用三栏骨架
layout_three_column.inc.htm,禁止自行写container/row/col-lg-*不需要左右栏时设
$sidebar_left_file=''和$sidebar_right_file='',中栏独占中间区域列表分隔用
py-*/mb-*间距,禁止border-top/border-bottom
部署注意
插件模板通过 _include() 加载,首次访问时自动编译缓存。如需强制刷新可清理:
rm -f tmp/plugin_xnx_gitee_projects_view_htm_*.htm

