QQ 登录互通能力(qq-mp)
微信小程序/小游戏 ⇄ 腾讯 QQ 小程序的登录互通封装。
零业务耦合,所有外部依赖(接口域名、post 方法、QQ 互联 AppId、storage、toast、玩家服务等)均由调用方注入。
文件结构
qq-mp/
├── index.ts 统一导出
├── types.ts 完整类型定义
├── qq-mp.ts 核心:登录跳转 / 票据提取 / 换取登录态
├── qq-mini-plugin.ts qq-wxmini-plugin 封装(QQ 用户访问微信小程序)
├── AccountService.ts 业务编排单例(切号入口 / wx.onLaunch / wx.onShow)
└── config.ts ⚠️ 仅作示例的常量文件,t-comm 内部不引用文档参考
一、微信 ⇄ QQ 登录切换
1. 跳转 QQ 小程序登录
import { launchQQMP } from 'module/auth/qq-mp';
launchQQMP({
qqAppId: 123456, // 必填:QQ 互联 AppId
// qqMpAppId: 'wx26da53d900421226', // 可选,默认值即此
// loginPath: 'pagesLogin/pages/login/login', // 可选
// forcePwd: false, // 可选,是否强制密码
// retcodes: [], // 可选,回传 QQ 小程序的状态码
// envVersion: 'trial', // 可选,体验版调试用
}).then(() => {
// 跳转成功
}).catch((err) => {
console.error(err);
});2. App.onShow 中提取票据
import { handleQQLoginOnShow } from 'module/auth/qq-mp';
// 微信小游戏入口(GooseHomeApp.ts 或 App.vue 等)
wx.onShow((options) => {
const ticket = handleQQLoginOnShow(options);
// ticket 含:qqOpenid / qqAccessToken / qqRefreshToken / qqAppid
if (!ticket) return;
// 调换取登录态接口(见下一节)
});handleQQLoginOnShow 默认会用 wx.setStorageSync 缓存票据,如需自定义存储:
import { handleQQLoginOnShow, QQ_TICKET_STORAGE_KEY } from 'module/auth/qq-mp';
import { oops } from 'core/Oops';
handleQQLoginOnShow(options, {
storage: {
set: (k, v) => oops.storage.set(k, v),
get: (k) => oops.storage.get(k),
remove: (k) => oops.storage.remove(k),
},
// storageKey: 'MY_QQ_TICKET',
});3. 用票据换取登录态
import { queryQQLoginUserInfo } from 'module/auth/qq-mp';
import { post } from 't-comm/es/cocos/manager/index.mjs';
import { isTestEnv } from 't-comm/es/cocos/env/index.mjs';
const res = queryQQLoginUserInfo({
// 必填:接口域名(支持函数动态返回)
host: () => isTestEnv() ? 'https://atest.igame.qq.com' : 'https://a.igame.qq.com',
// 必填:post 方法
post,
// 必填:票据信息
ticketInfo: ticket,
// 可选:兜底 QQ 互联 AppId(票据中无 appid 时使用)
fallbackQQAppId: 123456,
// 可选:接口路径,默认 pmdtrpc.commcgi.user.user/QueryUserInfo
// apiPath: '...',
// 可选:额外 query/reqData
// extraQuery: {},
// extraReqData: {},
});
res.then((data) => {
if (data?.user_info) {
console.log('切换 QQ 登录成功', data.user_info);
}
});4. 其它工具
import { readQQTicketInfo, clearQQTicketInfo } from 'module/auth/qq-mp';
const ticket = readQQTicketInfo(); // 从 storage 读已保存的票据
clearQQTicketInfo(); // 清除票据二、QQ 用户访问微信小程序(qq-wxmini-plugin)
1. 配置插件
manifest.json 的 mp-weixin 节点:
{
"mp-weixin": {
"plugins": {
"qq-wxmini-plugin": {
"version": "1.0.7",
"provider": "wx41f6784ad4052201"
}
}
}
}⚠️ 使用前需先在微信公众平台后台添加该插件的授权
2. 入口初始化与环境检测
import {
initQQMiniPlugin,
checkIsQQEnv,
clearWxLoginStorageIfQQEnv,
} from 'module/auth/qq-mp';
// onLaunch
initQQMiniPlugin();
// QQ 环境下若误用了微信登录态,清除 storage 重新走 QQ 登录
clearWxLoginStorageIfQQEnv({
isWxLoggedIn: () => {
// 业务自定义判断逻辑(如 cookie tip_utype === '2')
return /* ... */ false;
},
// clearStorage: () => oops.storage.removeAll?.(), // 可选
});
// 任意位置使用
if (checkIsQQEnv()) {
// 当前是 QQ 用户通过插件访问
}三、零耦合说明
本目录不依赖任何业务包:
| 外部依赖 | 注入方式 |
|---|---|
| 接口域名 | queryQQLoginUserInfo({ host })(支持字符串或函数) |
| post 请求方法 | queryQQLoginUserInfo({ post }) |
| QQ 互联 AppId | launchQQMP({ qqAppId }) |
| storage | handleQQLoginOnShow(_, { storage }) 可选 |
| 微信登录态判断 | clearWxLoginStorageIfQQEnv({ isWxLoggedIn }) |
仅依赖:
wx.*小程序/小游戏全局 APIrequirePlugin(小程序内置全局)
四、AccountService —— 高层业务编排(可选)
如果你不想自己拼装上面这些原子能力,可以直接用 AccountService 单例来完成 「微信 ⇄ QQ 登录态切换 + 启动期初始化」。它在内部组合调用了 launchQQMP / handleQQLoginOnShow / queryQQLoginUserInfo / clearQQTicketInfo / clearWxLoginStorageIfQQEnv / initQQMiniPlugin,并管理切号过程中的状态。
AccountService 同样零业务耦合,所有依赖通过 configure() 一次性注入。
1. 启动期注入依赖
import { AccountService } from 't-comm/es/qq-mp';
import { isQQAccount } from '@/api/account';
import { config as apiConfig } from '@/api/config';
import { oops } from '@/core/Oops';
import { ToastTip } from '@/core/render/ToastTip';
import { PlayerService } from '@/module/player/service/PlayerService';
import { isTestEnv } from 't-comm/es/cocos/env/index.mjs';
import { post } from 't-comm/es/cocos/manager/index.mjs';
const QQ_APP_ID = 123456; // 业务方自己的 QQ 互联 AppId
AccountService.configure({
qqAppId: QQ_APP_ID,
post,
getQQLoginUserInfoHost: () => isTestEnv()
? 'https://atest.igame.qq.com'
: 'https://a.igame.qq.com',
storage: {
set: (k, v) => oops.storage.set(k, v),
get: <T = any>(k: string) => oops.storage.get<T>(k),
remove: (k) => oops.storage.remove(k),
},
loginInfoStorageKey: apiConfig.loginInfoStorageKey,
isQQAccount, // () => boolean
toast: (msg) => ToastTip.show(msg),
clearPlayer: () => PlayerService.ins.clear(),
bootstrapPlayer: (opts) => PlayerService.ins.bootstrap(opts),
});2. 启动期入口
// onLaunch / Game.bootstrap
AccountService.ins.handleAppOnLaunch();内部会执行:
initQQMiniPlugin():初始化 qq-wxmini-plugin- 若处于 QQ 环境但本地却是微信登录态,自动清空
loginInfoStorageKey - 绑定
wx.onShow:从腾讯 QQ 小程序返回时自动提取票据 → 调queryQQLoginUserInfo换登录态 → 调clearPlayer+bootstrapPlayer({ force:true })
3. 设置页切号
// 「切换 QQ 账号 / 切换微信账号」按钮文案
const text = AccountService.ins.getSwitchButtonText();
// 切到 QQ 账号
AccountService.ins.switchToQQ();
// 切回微信账号
AccountService.ins.switchToWx();4. 自定义文案(可选)
configure 的第二个参数可覆盖默认中文文案:
AccountService.configure(deps, {
alreadyQQ: 'Already on QQ account',
alreadyWx: 'Already on WeChat account',
switchQQFail: 'Switch to QQ failed, please retry',
switchQQRetry: 'Switch to QQ failed, please try again',
switchQQSuccess: 'Switched to QQ account',
switchWxSuccess: 'Switched to WeChat account',
switchWxFail: 'Switch to WeChat failed, please retry',
});5. 完整依赖一览
| 依赖项 | 类型 | 说明 |
|---|---|---|
qqAppId | string | number | QQ 互联 AppId |
post | QQPostFn | 通用 post 方法(用于调 QueryUserInfo) |
getQQLoginUserInfoHost | string | () => string | QueryUserInfo 域名(可为函数以便区分测试 / 正式环境) |
storage | QQStorageLike | storage 适配器(一般桥接到 oops.storage) |
loginInfoStorageKey | string | loginInfo 在 storage 中的 key |
isQQAccount | () => boolean | 当前是否为 QQ 账号 |
toast | (msg: string) => void | 显示提示文案 |
clearPlayer | () => void | 清空玩家本地缓存 |
bootstrapPlayer | (opts: { force: boolean }) => Promise | void | 强制重新初始化玩家服务 |
未调用
AccountService.configure(deps)就使用AccountService.ins.*时会 抛出[AccountService] 未初始化错误,便于排错。
五、QQ App 环境直登路径(推荐 cocos-game 接入)
适用场景:用户在 QQ App 中通过
qq-wxmini-plugin访问微信小游戏(即checkIsQQEnv() === true)。 此时无需wx.navigateToMiniProgram跳转腾讯 QQ 小程序,可直接:
- 切 QQ 账号:
plugin.login()拿 QQ code → 业务后台_ltype=tiploginqqproc换登录态- 切微信账号:
wx.login()拿微信 code → 业务后台_ltype=tiploginwxproc换登录态文档参考:QQ-WXMINI-PLUGIN
1. 新增的原子能力
| 能力 | 说明 |
|---|---|
qqPluginLogin() | QQ App 环境下,通过 plugin.login() 同步拿到 QQ code |
wxLogin() | 通过 wx.login() 拿到微信 code(QQ App 下也可用) |
clearWxLoginStorageIfQQEnv({ shouldForceQQ }) | 新增 shouldForceQQ 开关,业务可控制启动期是否强制 QQ 登录 |
loginMp({ code, getCode, _ltype }) | cocos/login 的 loginMp 现已原生支持 QQ 登录:传 _ltype: 'tiploginqqproc' 配合 code 或 getCode 即可 |
import { qqPluginLogin, wxLogin } from 't-comm/es/qq-mp';
const { code: qqCode } = await qqPluginLogin(); // QQ App 下拿 QQ code
const { code: wxCode } = await wxLogin(); // 任意环境拿微信 code2. AccountService 新增三个可选依赖
| 依赖项 | 类型 | 说明 |
|---|---|---|
shouldForceQQ | () => boolean(可选) | 启动期是否强制 QQ 登录态,默认 () => true(保留旧行为)。若希望「QQ App 下也允许手动切到微信账号」,传 () => false,或基于业务自身的「用户是否手动选了微信」状态返回值 |
code2QQLogin | (code: string) => Promise | void(可选) | QQ App 下「切 QQ 账号」的后台探身入口。AccountService 会先用 qqPluginLogin() 拿 code 再交给它。不传则 fallback 到旧的 launchQQMP 跳转路径 |
code2WxLogin | (code: string) => Promise | void(可选) | QQ App 下「切微信账号」的后台探身入口。AccountService 会先用 wxLogin() 拿 code 再交给它。不传则 fallback 到旧的「清 storage + bootstrapPlayer」路径 |
三个依赖全部可选,未传时
AccountService行为与改动前完全一致。
3. switchToQQ / switchToWx 的环境分流
switchToQQ()
├── checkIsQQEnv() === true 且传了 code2QQLogin
│ → qqPluginLogin() 拿 QQ code → code2QQLogin(code) → bootstrapPlayer({ force:true })
└── 否则(微信宿主 / 未传 code2QQLogin)
→ launchQQMP() 跳腾讯 QQ 小程序 → wx.onShow 回到宿主 → 旧票据流程
switchToWx()
├── checkIsQQEnv() === true 且传了 code2WxLogin
│ → wxLogin() 拿微信 code → code2WxLogin(code) → bootstrapPlayer({ force:true })
└── 否则(微信宿主 / 未传 code2WxLogin)
→ 清 QQ 票据 + loginInfo → bootstrapPlayer({ force:true })六、cocos-game 业务方接入改动指引
cocos-game 业务方在 AccountService.configure() 时追加以下三个依赖即可(其它字段保持不变)。
业务后台 code 换登录态使用 t-comm 已有的
loginMp(_ltype=tiploginqqproc/_ltype=tiploginwxproc)。
1. 引入
import { AccountService } from 't-comm/es/qq-mp';
import { loginMp } from 't-comm/es/cocos/login/login';2. loginMp 已原生支持 QQ 登录
loginMp 现已支持两个可选参数,同时支持微信登录和 QQ 登录:
| 参数 | 类型 | 说明 |
|---|---|---|
code | string(可选) | 外部已拿到的 code(如 plugin.login() 拿到的 QQ code)。传了就跳过内部的 wx.login() |
getCode | () => Promise<{ code: string }>(可选) | 自定义 code 提供者。例如直接传入 qqPluginLogin |
code 来源优先级:code > getCode > 默认 wx.login()。
// 微信登录(默认行为,向后兼容)
loginMp({ url, appid });
// QQ 登录(QQ App 环境下,已有 plugin.login() 拿到的 QQ code)
loginMp({ url, appid, _ltype: 'tiploginqqproc', code: qqCode });
// QQ 登录(让 loginMp 自己去调 plugin.login)
import { qqPluginLogin } from 't-comm/es/qq-mp';
loginMp({ url, appid, _ltype: 'tiploginqqproc', getCode: qqPluginLogin });3. configure 追加字段
AccountService.configure({
// ===== 原有字段保持不变 =====
qqAppId: QQ_APP_ID,
post,
getQQLoginUserInfoHost: () => isTestEnv()
? 'https://atest.igame.qq.com'
: 'https://a.igame.qq.com',
storage,
loginInfoStorageKey: apiConfig.loginInfoStorageKey,
isQQAccount,
toast: msg => ToastTip.show(msg),
clearPlayer: () => PlayerService.ins.clear(),
bootstrapPlayer: opts => PlayerService.ins.bootstrap(opts),
// ===== 【新增】=====
// ① 是否「QQ App 下强制 QQ 登录态」
// 业务希望支持 QQ App 下手动切微信时,基于业务的「用户已手动切到微信」状态返回值
// 例如以 storage 记一个 user_picked_wx 标记
shouldForceQQ: () => !storage.get('user_picked_wx'),
// ② QQ App 下「切 QQ 账号」走业务后台 _ltype=tiploginqqproc
// AccountService 会先用 plugin.login() 拿 QQ code,再调用此函数
// 业务侧只需把 code 透传给 loginMp 的 code 参数即可
code2QQLogin: (code) => loginMp({
url: getApiHost() + '/login',
appid: WX_APP_ID,
_ltype: 'tiploginqqproc',
code, // ← 直接用 plugin.login() 拿到的 QQ code
storage,
storageKey: apiConfig.loginInfoStorageKey,
onLoginInfo: (info) => {
storage.remove('user_picked_wx'); // 切回 QQ 时清掉微信偏好
updateLocalLoginInfo(info);
},
}),
// ③ QQ App 下「切微信账号」走业务后台 _ltype=tiploginwxproc
// AccountService 会先用 wx.login() 拿微信 code,再调用此函数
code2WxLogin: (code) => loginMp({
url: getApiHost() + '/login',
appid: WX_APP_ID,
_ltype: 'tiploginwxproc',
code, // ← 直接用 wx.login() 拿到的微信 code
storage,
storageKey: apiConfig.loginInfoStorageKey,
onLoginInfo: (info) => {
storage.set('user_picked_wx', true); // 记下用户主动选了微信
updateLocalLoginInfo(info);
},
}),
});4. 切号 UI 接入
切号按钮无需任何改动,依然调用:
AccountService.ins.switchToQQ(); // → QQ App 下走 plugin.login + code2QQLogin
AccountService.ins.switchToWx(); // → QQ App 下走 wx.login + code2WxLoginAccountService 内部会根据 checkIsQQEnv() 自动分流到「直登路径」或「跳腾讯 QQ 小程序路径」。
5. 兼容性与回滚
- 不传
shouldForceQQ/code2QQLogin/code2WxLogin:行为与改动前完全一致 - 现有所有
qq-mp单测(28 + 25 + 27)全部通过,无破坏性变更