XIUNOX 更新日志 2026-07-29 第三方登录插件优化

管理员组

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.php

    • callback 成功后检测 action=pending_bind,跳转到 oauth-bind-select

    • case '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 个位置:

  1. my 个人主页侧栏菜单 → 独立项目页面

  2. user 公开主页 tab → 项目列表

  3. 帖子详情页右侧栏作者卡片 → 项目摘要(最多 3 个)

  4. 独立页面 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 片段端点

使用前提

  1. 已安装并启用 xnx_oauth 插件

  2. 用户已绑定 Gitee 账号

  3. 需在后台插件管理中安装本插件

注意事项

  • 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.jsondependencies 字段使用了索引数组格式 ["xnx_oauth"],但插件系统 plugin_dependencies() 函数(model/plugin.func.php:209)按关联数组 $_dir => $version 遍历。索引数组传入后 $_dir 变成数字 0(而非 "xnx_oauth"),导致 isset($plugins[0]) 为 false,依赖检查永远失败,启用时报"缺少依赖"错误。

改动方案

  1. 修插件:将 conf.json 的 dependencies 从索引数组改为推荐的 object 格式

  2. 增强系统:在 plugin_dependencies() 中增加格式规范化逻辑,同时兼容两种写法

修改文件

  • plugin/xnx_gitee_projects/conf.json

    • dependencies["xnx_oauth"] 改为 {"xnx_oauth": "*"}

  • model/plugin.func.phpplugin_dependencies() 函数)

    • 新增格式规范化逻辑:遍历 dependencies 时检测 key 是否为 int,索引数组的 value 当作插件目录名、版本约束视为 *;关联数组原样保留

  • docs/plugindev/02-plugin-structure.md

    • dependencies 字段类型从 object 改为 object | array,补充两种格式说明和版本约束语义

  • docs/plugindev/01-architecture.md

    • 依赖章节补充兼容格式示例和规范化逻辑说明

  • docs/xiunox-plugin-dev/SKILL.md

    • conf.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',导致请求落入默认分支报"参数错误"。

改动方案

  1. 修插件:重写路由逻辑,用 param(1) 取主动作 + param(2) 取子动作的嵌套模式

  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 的规范。

改动方案

  1. 文档规范:在 4 处文档中新增布局与 Card 规范章节

  2. 插件修复:将 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 组合,禁止裸用 cardborder/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
最新回复

请先登录后再回复 登录