简介:面向Discuz!论坛站长与社区运营者的QQ快捷登录插件包,版本为北岸改进版2.4,主要解决论坛注册流程繁琐、QQ用户登录不便的问题。压缩包共61个文件,约1MB,包含24个PHP程序文件、12张JPG与11张PNG图片、8个HTM页面、4个XML配置及少量GIF/HTML素材,覆盖插件安装卸载、QQ绑定与回调、手机端登录类、多编码插件配置、模板与帮助说明等模块,结构紧凑,适合直接部署或二次调试。已有430人学习下载。插件在原有QQ登录基础上做了可用性与兼容性优化,宣称100%可用;使用前需在腾讯开放平台申请QQ登录IDKEY并填入配置。读者可据此完成论坛账号与QQ账号的对接,减少重复注册环节,同时参考其安装、卸载、绑定、回调和多编码适配思路,排查登录失败与版本兼容问题,提升社区用户登录体验与留存。
1. 一份针对登录环节的效率工具
论坛后台的注册转化数据掉到个位数的时候,最先被怀疑的往往不是内容质量,而是那个要填七八项的注册表单。DZ QQ登录插件 北岸QQ登录 改进版 2.4 处理的就是这一步:把「用户名、密码、邮箱、验证码、同意协议」这一串动作压缩成一次授权点击,用户回来时本地账号已经建好、cookie 已经种上、UCenter 那边也同步完了。它面向的是还在跑 DZ X3.4 / X3.5 系列、有存量用户、又不能推倒重来的站点——这类站点最怕的不是功能少,而是加功能把老账号体系搞乱。2.4 这个版本号值得单独拿出来讲,是因为它在「已登录用户再点授权」和「同一 openid 重复绑定」这两条路径上做了收口,而这两条恰恰是老版本最容易出问题的地方。下面按「目录结构 → 授权链路 → 参数配置 → 绑定改造 → 排错 → 验证」的顺序拆。
2. 拆开插件目录:文件结构与 OAuth 授权链路
拿到一个 DZ 插件包,先看目录,不看代码。目录结构基本决定了它能做什么、改动会扩散到哪里。这个 2.4 改进版的结构和老版本差别不大,主要多了一个独立的绑定分支文件和一份升级脚本,说明它在处理存量数据迁移。
2.1 目录结构与各文件职责
DZ 插件的落点是固定的:source/plugin/{identifier}/,其中identifier是插件唯一英文标识,全小写、下划线分隔。这个标识会同时出现在三处——目录名、配置文件discuz_plugin_{identifier}.xml的文件名、以及后台 URL 里的id=参数。三处对不上,插件在后台就显示不出来。
常见的目录长这样:
source/plugin/qa_qqlogin/ ├── discuz_plugin_qa_qqlogin.xml # 插件主配置:名称、版本、钩子、插件变量 ├── install.php # 安装时执行:建表、写默认变量 ├── uninstall.php # 卸载时执行:删表、清缓存 ├── upgrade.php # 2.x 升级到 2.4 时补字段、补索引 ├── plugin.php # 入口分发,按 $_GET['op'] 路由 ├── callback.inc.php # 授权回调:收 code、换 token、拉 openid ├── bind.inc.php # 绑定页:老用户输账号密码,新用户走注册 ├── function_qqlogin.php # 公共函数:签名、请求、日志 ├── table/ │ └── table_plugin_qqlogin_bind.php # C::t() 的表模型定义 ├── template/ │ ├── login.htm # 登录按钮/跳转页 │ └── bind.htm # 绑定表单 ├── language/ │ └── lang_plugin_qqlogin.php # 语言包,避免中文硬编码 └── admin/ └── admin_qqlogin.inc.php # 后台管理页,看绑定记录、清日志逐个说职责。discuz_plugin_qa_qqlogin.xml是整个插件的身份证,里面<plugin>节点描述基本信息,<var>节点定义后台可见的插件变量(AppID、AppKey、回调地址这些就放这儿),<hooks>节点声明挂哪些钩子。2.4 版把 AppKey 从硬编码挪进了插件变量,这个改动看着小,实际解决的是换号要改文件的问题。
callback.inc.php是整条链路里最不能出错的文件。它只做三件事:校验 state、拿 code 换 access_token、拿 openid 查本地绑定表。任何一件失败都要能给出人能看懂的错误页,而不是白屏。
bind.inc.php是分支最多的地方。老用户绑已有账号、新用户自动注册、已登录用户再点授权、被封禁用户——四条路都从这里分出去。
table/table_plugin_qqlogin_bind.php容易被忽略。DZ 的C::t()不是凭空变出来的,它要求source/plugin/{identifier}/table/下有对应命名的表模型文件,类名必须是table_plugin_qqlogin_bind,否则C::t('plugin_qqlogin_bind')直接抛异常。
提示:拿到包先核对
identifier三处一致。后台「应用中心 → 插件」里看不到插件,九成是这个原因,跟 PHP 版本、权限都没关系。
2.2 授权码链路在 DZ 里的落地顺序
OAuth 的授权码模式本身不复杂,难点在于 DZ 是无框架的面向过程代码,$_G全局、cookie、session、缓存三套东西混在一起,顺序错一步就是「授权成功但回来还是游客」。
完整链路按时间顺序是:
- 游客点「一键登录」,
plugin.php?id=qa_qqlogin生成一个随机state,写进 cookie(不用 session,DZ 的 session 在某些关闭 SESSION 的站点上不可靠)。 - 302 跳到第三方开放平台的授权页,带上
appid、redirect_uri、state、response_type=code。 - 用户在那边确认授权,浏览器回到
redirect_uri,URL 上带code和state。 callback.inc.php先比对state与 cookie 里的值。不一致直接拒绝——这一步是防跨站伪造回调的最低成本手段。- 服务端用
code+appid+appkey换access_token。换 token 必须在服务端做,AppKey 一旦出现在浏览器端就等于公开。 - 用
access_token拉openid,拿openid查common_member_connect或插件自建绑定表。 - 命中则直接建立会话;未命中则跳到
bind.inc.php走绑定或注册。 - 建立会话后,如果站点开了 UCenter 同步登录,还要额外调一次同步接口。
第 5 步的实现大概是这样:
// function_qqlogin.php 片段,服务端换 token function qqlogin_get_token($code) { global $_G; $plugin = $_G['cache']['plugin']['qa_qqlogin']; // 插件变量已进缓存 $params = array( 'grant_type' => 'authorization_code', 'client_id' => $plugin['appid'], 'client_secret' => $plugin['appkey'], // 只在服务端出现 'code' => $code, 'redirect_uri' => $plugin['callback_url'], // 必须与授权时完全一致 'fmt' => 'json', ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://授权域名/oauth2.0/token'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 超时必须设,不能无限等 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 生产环境别关校验 $ret = curl_exec($ch); $errno = curl_errno($ch); curl_close($ch); if ($errno) { qqlogin_log('curl_fail', $errno); // 落日志,不把 errno 吐给用户 return array('errcode' => -1, 'errmsg' => '网络异常,请稍后重试'); } return json_decode($ret, true) ?: array('errcode' => -1, 'errmsg' => '返回格式异常'); }关键点逐个说。CURLOPT_TIMEOUT设 5 秒是有原因的:第三方接口偶发慢响应时,不设超时会让 PHP-FPM 进程挂在那儿,几个并发就能把进程池吃干。CURLOPT_SSL_VERIFYPEER保持 true,本地调试可以临时关,但要在代码注释里写清楚,别一带上线就忘了。
redirect_uri必须和发起授权时用的一模一样,包括末尾斜杠、大小写、协议。这是后面第 5 章要重点说的问题,很多「参数错误」都出在这儿,跟代码逻辑没关系。
第 6 步拿到openid之后,注意它和unionid是两个层级的东西:同一个用户在 PC 站和移动站可能有不同openid,但unionid一致。如果插件只存openid,多端打通就会翻车。2.4 版在绑定表里留了unionid字段,用不用取决于后台开关。
2.3 数据落点:用 DZ 自带表还是自建表
DZ X 系列自带common_member_connect表,字段大致包含uid、openid、conisregister、conisbind、conuintoken。它的设计初衷就是给第三方登录用的,所以第一选择永远是复用它——新建表意味着要自己维护建表、升级、删除三套脚本,还得处理数据一致性。
不过 2.4 版额外建了一张plugin_qqlogin_bind表,理由是两个:一是要记录绑定时间、来源端(PC/移动)、最近登录 IP 这些审计信息,自带表没这些字段;二是要避免直接改 DZ 核心表结构,以后升级 DZ 版本时不用同步改表。
-- install.php 里执行,注意前缀用 pre_ 只是示例,实际取 $_G['config']['db'][1]['tablepre'] CREATE TABLE IF NOT EXISTS `pre_plugin_qqlogin_bind` ( `bid` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` int(10) unsigned NOT NULL DEFAULT '0', `openid` varchar(64) NOT NULL DEFAULT '', `unionid` varchar(64) NOT NULL DEFAULT '', `source` tinyint(1) NOT NULL DEFAULT '1', -- 1=PC 2=移动 `dateline` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`bid`), UNIQUE KEY `openid` (`openid`), -- 唯一索引是防重复绑定的关键 KEY `uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;openid上加唯一索引这一行是重点。没有它,两个请求同时回调(用户连点两次、或者浏览器重试)就会插入两条记录,后续查绑定关系时fetch_by_openid返回的是一条随机记录,表现出来就是「明明绑过了还让我绑」的血泪经验。加了唯一索引以后,第二次插入直接失败,代码里捕获这个失败做「已绑定则直接登录」即可。
source字段是做运营分析用的,别小看它——上线一个月后想知道移动端占比、决定要不要单独优化移动端流程,就靠这个字段。
3. 安装与参数配置:AppID、回调地址、插件变量一次配对
插件装上去只是开始,能不能跑通八成取决于参数配置。这一章把配置拆成「环境自查 → 变量逐项 → 一致性校验」三步,每一步都有明确的通过标准,避免装完以后靠猜。
3.1 装之前先做环境自查
很多「装完打不开」的问题,根源在环境阶段就已经埋下了。安装前按下面这张表过一遍,比装完再排查省时间。
| 检查项 | 查看位置 / 命令 | 期望结果 |
|---|---|---|
| PHP 版本 | 后台「站长 → 环境检测」 | 7.4 及以上,8.x 需确认插件无each()等废弃函数 |
| curl 扩展 | 环境检测页 | 已开启 |
| openssl 扩展 | 环境检测页 | 已开启(HTTPS 回调必需) |
| 站点 URL | 后台「全局 → 站点信息」 | 带 https,与真实访问域名完全一致 |
| 插件目录权限 | ls -ld source/plugin/qa_qqlogin | 属主为 Web 用户,755 |
| 缓存目录可写 | ls -ld data/cache data/log | 可写,否则插件变量读不到 |
| 伪静态 / 路由 | 访问plugin.php?id=qa_qqlogin | 不 404 |
重点说两个。第一,data/cache不可写的时候,插件变量写进数据库了但缓存没刷新,$_G['cache']['plugin']['qa_qqlogin']取到空数组,表现出来是「AppID 明明是空的」——其实数据库里有,只是缓存没读。清缓存的办法是在后台「工具 → 更新缓存」,或者手动删data/cache/cache_plugin.php。
第二,站点 URL 这一项。DZ 的$_G['siteurl']是从后台配置推出来的,不是从$_SERVER['HTTP_HOST']推出来的。如果站点跑在反向代理后面、或者后台填的是http://而实际访问是https://,siteurl就会和真实地址对不上,回调地址跟着错。常见做法是后台站点 URL 和实际访问地址严格保持一致,加不加 www 也统一。
3.2 后台插件变量逐项配置
插件变量定义在 XML 的<var>节点里,安装时写进common_pluginvar,后台「插件 → QQ登录 → 设置」就能看到。2.4 版的变量大致是这些:
| 变量名 | 含义 | 推荐值 | 容易错的地方 |
|---|---|---|---|
appid | 开放平台分配的应用 ID | 原样粘贴 | 前后带空格,肉眼看不出来 |
appkey | 应用密钥 | 原样粘贴 | 曾有版本误把密钥写进模板,2.4 已修 |
callback_url | 授权回调地址 | https://站点/plugin.php?id=qa_qqlogin:callback | 必须与平台后台登记的完全一致 |
allow_register | 未绑定时是否自动注册 | 1(开) | 关掉后新用户点登录会卡在绑定页 |
bind_force | 是否强制绑定已有账号 | 0 | 开 1 时老用户必须先输密码 |
sync_ucenter | 是否同步 UCenter 登录 | 1(有 UCenter 时) | 关掉后其他应用不认这个登录态 |
debug_log | 是否写调试日志 | 调试期 1,上线后 0 | 长期开着日志文件会撑爆磁盘 |
unionid_mode | 是否按 unionid 归并 | 0 | 单端站点没必要开 |
callback_url这一项值得单独讲。它由三部分拼成:$_G['siteurl']+plugin.php?id={identifier}:{script}。script部分对应source/plugin/qa_qqlogin/下的callback.inc.php,去掉.inc.php,也就是callback。所以完整 URL 是https://你的域名/plugin.php?id=qa_qqlogin:callback。这个值要原封不动填到开放平台的应用配置里,两边差一个字符都不行。
allow_register和bind_force的组合决定了新老用户走哪条路,第 4 章会展开。这里只要记住:bind_force=1且allow_register=0是最严格的配置,只有已有账号的人能用,适合封闭社区;bind_force=0且allow_register=1最宽松,适合拉新。
3.3 回调地址、cookie 域、协议三者的一致性校验
这一节是全篇最容易被跳过、又最容易出问题的地方。三个东西必须对齐:回调地址、cookie 作用域、页面协议。
cookie 的两个参数在 DZ 里是全局配置:cookie_domain和cookie_path。前者留空表示「当前域名」,填.example.com表示所有子域共享。如果站点有 www 和裸域两个入口,而cookie_domain只填了其中一个,用户从另一个入口回来就是未登录状态。表现出来是「授权成功了,跳回来还是游客」,日志里却一切正常——最典型的问题。
协议不一致更隐蔽。页面是 https,回调地址填的 http,浏览器在跳转时可能被拦,或者 cookie 的 secure 属性导致写不进去。检查办法很直接:
# 1. 看当前页面实际协议与 cookie 域 curl -sI https://你的域名/plugin.php?id=qa_qqlogin | grep -i 'set-cookie' # 2. 确认回调地址在平台侧登记的值(对照后台插件变量) # 直接在浏览器访问下面这个地址,看是否返回插件自己的错误页而不是 404 curl -sI 'https://你的域名/plugin.php?id=qa_qqlogin:callback' # 3. 检查 cookie 配置(后台「全局 → 站点信息 → cookie 域名」) # 留空则仅当前域名生效;多入口站点建议显式写 .你的域名第二条命令的意义在于:如果这个 URL 直接 404,说明plugin.php的路由没生效——可能是伪静态规则把plugin.php也重写了,也可能是callback.inc.php文件名写错(比如写成了callback.php,DZ 只认*.inc.php)。这一步能在配置阶段就把问题挡掉,不用等用户点登录才发现。
注意:改动
cookie_domain或站点 URL 之后,一定要清一次缓存并让所有用户重新登录,旧的 cookie 不会自动迁移。
4. 绑定与注册逻辑:把「一键登录」做成可回滚的账号映射
授权链路通了,只是拿到了一个openid。真正决定用户体验的是接下来这一步:这个openid对应本地哪个uid。设计得糙,就会出现「一个人两个号」「老用户被强制注册新号」「解绑后找不回账号」这三类投诉。
4.1 首次登录的三条分支
bind.inc.php进来以后,按顺序判断,三条分支互斥:
分支一:绑定表命中。C::t('plugin_qqlogin_bind')->fetch_by_openid($openid)有结果,取出uid,再查common_member确认账号存在且未被删除、未被禁封。都存在就直接建会话,用户无感。这是绝大多数回访用户走的路,必须最快、最少查库。
分支二:未命中但当前已有登录态。用户先账号密码登录了,又点了授权按钮。这时候不应该新建账号,而是把当前uid和这个openid关联起来。2.4 版专门处理了这个场景,老版本会错误地新建一个账号,用户一脸懵。
分支三:未命中且是游客。这时候才轮到allow_register和bind_force起作用。bind_force=1就展示绑定表单让用户输已有账号密码;bind_force=0则按allow_register决定是自动生成用户名注册,还是也走绑定表单。
自动注册这一条要格外小心。生成用户名的常见做法是qq_+ 随机串,但必须做重名检测,并且注册后要写common_member、common_member_status、common_member_count、common_member_profile四张表——少写一张,用户在个人中心页就会报错。DZ 自带的注册函数uc_user_register()会把这几张表都处理掉,比自己拼 SQL 稳。
4.2 回调控制器里的绑定主逻辑
把上面三条分支落到代码,结构大致如下:
// callback.inc.php 片段,绑定分支 $openid = qqlogin_get_openid($token); // 上一步换到的 openid if (!$openid) { showmessage('qqlogin_openid_fail', '', array(), array('login' => 1)); } $bind = C::t('#qa_qqlogin#plugin_qqlogin_bind')->fetch_by_openid($openid); if ($bind && ($member = getuserbyuid($bind['uid']))) { // 分支一:已绑定,直接建会话 if ($member['status'] == -1) { // 被封禁账号不放行 showmessage('qqlogin_member_banned'); } setloginstatus($member, 2592000); // 30 天免登录 qqlogin_sync_ucenter($member); // 按开关决定是否同步 qqlogin_log('login_ok', $member['uid']); showmessage('qqlogin_login_success', 'forum.php', array(), array('locationtime' => 1)); } elseif ($_G['uid']) { // 分支二:已登录用户补绑定,不新建账号 C::t('#qa_qqlogin#plugin_qqlogin_bind')->insert(array( 'uid' => $_G['uid'], 'openid' => $openid, 'unionid' => $unionid, 'source' => $source, 'dateline' => TIMESTAMP, ), false); // false 表示不检查重复,靠唯一索引兜底 showmessage('qqlogin_bind_success', 'home.php?mod=space'); } else { // 分支三:游客,按开关走绑定页或自动注册 if ($_G['cache']['plugin']['qa_qqlogin']['bind_force']) { include template('qa_qqlogin:bind'); // 展示绑定表单 } else { qqlogin_register_and_bind($openid, $unionid); // 内部调 uc_user_register } }几个参数说清楚。setloginstatus($member, 2592000)的第二个参数是 cookie 有效期,单位秒,2592000是 30 天,0表示浏览器关闭即失效,-1表示只在本 session 内有效。站点如果对安全要求高,建议设成86400(一天)配合「记住我」单独判断。
C::t('#qa_qqlogin#plugin_qqlogin_bind')这个语法要注意:带#前缀是告诉 DZ 这是插件表,去source/plugin/qa_qqlogin/table/下找表模型,而不是去source/class/table/。前缀写错会直接 fatal error,报「table does not exist」,但实际是模型文件没找到。
insert(..., false)的第二个参数是「是否检查数据合法性」,设 false 跳过字段校验、靠数据库唯一索引兜底,能扛住并发重复插入。这里其实有个小问题:如果唯一索引冲突,DZ 会抛出异常而不是返回 false,生产环境要么 try-catch,要么先查后插再捕获。
qqlogin_sync_ucenter()只在sync_ucenter开关打开时执行,内部调uc_user_synlogin($uid),把同步脚本输出到页面底部。如果站点没装 UCenter 或者关闭了同步登录,这个函数要能安全返回,不能报错——判断依据是$_G['setting']['ucenteron']。
4.3 会话建立之后的三件事
setloginstatus()只是把 cookie 种上,还有三件事容易漏。
第一件,$_G['uid']和$_G['username']在当前请求里不会自动更新,如果建会话之后还要在同一请求里读用户信息,得手动赋值或者重新getuserbyuid()一次。否则会出现「登录成功页显示的是游客昵称」这种小问题。
第二件,登录日志。DZ 有common_statuser表记录每日登录,插件建会话后应当补一条,否则后台的「会员活跃统计」会漏掉这部分用户。常见做法是调updatestat('login')。
第三件,积分和任务。如果站点配了「每日登录加分」或者「首登任务」,会话建起来以后不会自动触发,需要在绑定成功后手动执行对应的hook或直接调积分函数。这块插件一般留了开关,默认关,因为不同站点规则差异太大。
提示:调试绑定逻辑时,先把
debug_log打开,日志写在data/log/qqlogin_YYYYMM.php。里面至少记录 openid 前 6 位、分支命中情况、返回的 uid,够定位问题又不至于泄露完整标识。
5. 避坑清单:五类常见故障与排查路径
下面这五条都是我在实际站点上遇到过的,按现象、原因、解决三段写,遇到同类问题可以直接对号入座。
第一条:回调页报「redirect_uri 参数错误」,但地址肉眼看着一样。
现象是授权页点确认后直接跳回一个错误页,插件自己的日志里连请求都没进来。原因是回调地址在平台侧登记的值和插件变量里的值存在不可见差异——末尾多了斜杠、http与https、带不带www、有没有被 CDN 改写成大写。解决方式是把两边的值复制出来做逐字符比对,echo -n加| md5sum最快。改完记得清data/cache。
第二条:授权成功跳回来,页面提示登录成功,刷新一下又变游客。
现象很典型。原因通常是 cookie 域不匹配:用户在www.域名发起授权,回调落在域名(或者反过来),cookie 写到了另一个域下。也可能是cookie_path被设成了子目录。排查办法是在回调页var_dump($_COOKIE),看有没有auth和saltkey。没有就是没写进去;有但下次请求没了,就是域或路径的问题。解决是把cookie_domain显式写成.你的域名,并统一入口。
第三条:同一个用户反复被要求绑定,绑定表里出现多条记录。
原因是openid上没有唯一索引,或者插入代码用了INSERT IGNORE之外的方式但没做前置查询,两个并发请求各插了一条。也可能是查绑定关系时按uid查而不是按openid查。解决分两步:先加唯一索引并清理历史重复数据(保留最早那条),再在插入处捕获唯一键冲突,冲突时降级为「查出来直接登录」。
第四条:后台改了 AppID 或回调地址,前台行为完全没变。
原因是 DZ 的插件变量走缓存,写库成功但data/cache/cache_plugin.php没更新。常见触发场景是手动改数据库、或者迁移站点时只拷了库没清缓存。解决办法固定为「改完 → 后台更新缓存 → 强刷浏览器」。迁移站点时更稳妥的做法是直接删data/cache/下所有文件,让 DZ 重建。
第五条:手机端授权完成后样式全乱,或者跳到了 PC 版页面。
原因是模板没有按端区分。DZ 的模板机制里,template/qa_qqlogin/login.htm是通用模板,移动端要放在template/qa_qqlogin/touch/下。如果只做了 PC 模板,移动端会回退到默认风格,布局自然对不上。另外模板有编译缓存,改完.htm要在后台「模板 → 更新缓存」或者删data/template/下对应文件,否则改了看不到效果。解决办法是把移动端模板补齐,并在bind.inc.php里用$_G['mobile']判断加载哪套。
6. 上线前的回归清单与一个保守习惯
插件跑通不等于能上线。第三方授权链路涉及外部服务、浏览器跳转、cookie 三个变量,任何一个在测试环境正常、生产环境都可能变。我一般会走一遍固定回归,八条,二十分钟能做完:
| 序号 | 场景 | 预期结果 |
|---|---|---|
| 1 | 全新游客点登录并授权 | 自动注册成功,跳首页,显示已登录 |
| 2 | 已登录用户点授权 | 补绑定到当前账号,不新建 uid |
| 3 | 已绑定用户再次授权 | 直接登录,无绑定页 |
| 4 | 绑定页输错密码 | 提示错误,不消耗绑定次数 |
| 5 | 绑定页输对密码 | 绑定成功,跳个人中心 |
| 6 | 被封禁账号授权 | 拒绝登录,给出明确提示 |
| 7 | 手机端全流程 | 页面正常,登录态生效 |
| 8 | 关闭sync_ucenter后再走一遍 | 不报错,其他应用不同步但不影响主站 |
第 8 条最容易被跳过,但它是「开关能关」的证明。插件里所有开关都该有「关掉也不炸」的保证,否则线上出问题想临时关掉都没退路。
再讲一个我一直在用的习惯:新逻辑一律用插件变量做灰度分流,不做全量替换。2.4 版处理「已登录用户再点授权」那段分支时,可以加一个bind_v2开关,默认0,先给一个测试账号所在的用户组打开,观察一周日志里分支二的命中次数和异常数,再全量。具体做法是在分支判断前插一行:
// 灰度开关:bind_v2 为 0 时走老逻辑,为 1 时走新分支 $useNewBind = !empty($_G['cache']['plugin']['qa_qqlogin']['bind_v2']) && in_array($_G['groupid'], array(1, 20)); // 1=管理员 20=内测用户组 if ($useNewBind && $_G['uid']) { // 新分支:补绑定,不新建账号 } else { // 老逻辑:保持原样,出问题可秒回滚 }这段的价值不在代码本身,在于它给了你一个不用发版就能回滚的后悔药。第三方登录这类依赖外部服务的功能,最怕的就是上线后才发现某类账号走不通,而回滚要重新打包上传——有开关的话,后台点一下就能退回老逻辑,用户完全无感。
统计口径也要提前定好。我一般在日志里固定打三个字段:branch(命中哪条分支)、uid(最终落到的账号)、cost_ms(从进回调到建会话的耗时)。一周后看branch的分布,如果分支二占比异常高,说明大量用户先登录再点授权,那前端按钮的展示位置就该调整了;如果cost_ms的 95 分位超过 800ms,问题基本在换 token 那一步的网络耗时上,该考虑加本地缓存或者换机房线路。
上次有个站点上线两周后发现绑定率只有三成,日志一拉,branch全是三、errcode集中在一个「用户名已存在」的分支上——自动注册生成的qq_前缀撞上了存量用户。改了个前缀规则就好了。从那以后我每次接这类第三方登录,都强制先把「已注册用户名规则」和「存量用户昵称分布」对一遍,再谈代码。希望帮到你。
本文还有配套的精品资源,点击获取