持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
第三方登录 —— 以QQ登录为例
参考文档
开发者流程
- 准备一个已经备案的网站需要有QQ登录的逻辑(登录页面,回跳页面)
- 然后在QQ互联上进行 身份认证 ,并且 审核通过
- 在QQ互联上创建应用,应用需要域名,备案号,回调地址等
- 等待人工审核,审核通过会得到
应用ID应用key回调地址
核心步骤
- 修改
vite.config.ts配置。 - 修改电脑的
host文件。 - 配置
VueRouter和vue组件。
修改 vite 配置
查看 vite 官网,有很多关于 vite 做服务器时相关选项,如下图所示,感兴趣的可以自行去阅读。
根据文档,修改 vite.config.ts 文件,修改端口号,基准路径,开启跨域等:
export default defineConfig({
// 配置开发服务器
server: {
// QQ三方登录的回调uri为:http://www.corho.com:8080/#/login/callback
// vite 中配置: www.corho.com:8080
host: "www.corho.com",
port: 8080,
// 其他有价值的配置项
open: true, // 帮我们打开浏览器
cors: true, // 允许开发时 ajax 跨域
},
});
修改 host 文件
这一步必须操作,如果不设置,会从互联网中寻找网站地址,而不是从本地去寻找。
注意:
做这一步会有杀毒软件爆出警告,因为早期的病毒就是通过修改
host文件让用户访问假网站,而这次是我们自己的操作,因此可以完全信任。
- 找到 C:\Windows\System32\drivers\etc 下 hosts 文件
- 在文件中加入
127.0.0.1 www.corho.com - 保存即可
如果提示没有权限
- 将hosts文件移到桌面,然后进行修改,确认保存。
- 将桌面hosts文件替换c盘文件
小技巧:如果不想让女朋友用自己的电脑逛淘宝,可以设置 127.0.0.1 www.taobao.com,这样他搜索淘宝只会跳转到本地自己搭建的服务器(每日一个分手小技巧)
关于mac OS 系统操作方法:
- 打开命令行窗口
- 输入:sudo vim /etc/hosts
- 按下:i 键
- 输入:
127.0.0.1 www.corho.com - 按下:esc
- 按下:shift + :
- 输入:wq 回车即可
配置组件
配置一个组件用于书写QQ登录的页面,配置路由。
DNS解析概念
DNS解析: 将域名解析成ip地址的过程。
想看一个网站 www.jd.com ,电脑不知道什么是 www.jd.com,需要询问的
-
先问本地 hosts 文件(一般不改) 如果本地配置了 域名 和 地址的映射关系,优先使用 hosts 中的映射(如下所示)
127.0.0.1 www.jd.com -
如果本地hosts文件里面没配(默认一般都没配)比如:找www.baidu.com
会找线上的 dns 服务器, dns 服务器就像一个字典, 字典中记录大量的 网站域名 和 网站ip 的对应关系 dns 服务器 112.80.248.75 www.baidu.com xxx.xx.xxx.xx www.xxx.com
登录实现
首先需要在 index.html 中引入QQ登录的第三方包
<script src="http://connect.qq.com/qc_jssdk.js" data-appid="100556005" data-redirecturi="http://www.corho.com:8080/#/login/callback"></script>
然后需要用到几个 API 获取用户的相关信息。
需要用到的 3 个 API
QC.Login.check:检查用户是否登录QC.Login.getMe:获取 QQ 用户唯一标识 openIdQC.api("get_user_info").success:获取信息
查阅 官方文档,找到需要的方法相关示例,代码如下所示,每个代码的作用见注释。
// 1. 检查用户是否已登录
if (QC.Login.check()) {
// 2. 获取 QQ 用户唯一标识 openId
QC.Login.getMe((openId) => {
console.log("openId", openId);
});
// 3. 获取用户资料
QC.api("get_user_info").success((res: unknown) => {
console.log("获取用户资料", res);
});
}
运行起来发现虽然控制台有打印效果了,但是编译器却在报错,查看报错,有 ESlint 报错,也有 ts 报错。一一解决。
eslint报错是因为没有这个全局变量,在eslint文件添加全局变量。
// eslintrc.cjs
module.exports = {
...
// 全局变量
globals: {
QC: true,
},
}
ts则是类型报错,需要给 QC 变量添加类型声明。
// env.d.ts
// QC 类型声明 - QQ 登录模块
declare namespace QC {
const Login: {
// QC.Login.check()
check: () => boolean;
// QC.Login.getMe((openId) => {
// console.log("获取QQ用户openId", openId);
// });
getMe: (callback: (openId: string) => void) => void;
};
// QC.api("get_user_info").success((res: unknown) => {
// console.log("获取QQ用户资料", res);
// });
function api(s: string): {
success: (res: unknown) => void;
};
}
渲染用户信息
声明类型
打印返回值的数据,查看其类型,创建数据对象的 ts 类型。
export interface Data {
ret: number;
msg: string;
is_lost: number;
nickname: string;
gender: string;
gender_type: number;
province: string;
city: string;
year: string;
constellation: string;
figureurl: string;
figureurl_1: string;
figureurl_2: string;
figureurl_qq_1: string;
figureurl_qq_2: string;
figureurl_qq: string;
figureurl_type: string;
is_yellow_vip: string;
vip: string;
yellow_vip_level: string;
level: string;
is_yellow_year_vip: string;
}
export interface UserInfo {
status: string;
fmt: string;
ret: number;
code: number;
data: Data;
seq: string;
dataText: string;
}
渲染信息
在组件中引入类型,创建一个变量存储数据的返回值。
import { ref } from 'vue';
import type { UserInfo } from '@/type';
// 1. 检查用户是否已登录
if (QC.Login.check()) {
// 2. 获取 QQ 用户唯一标识 openId
QC.Login.getMe((openId) => {
console.log('openId', openId);
});
// 3. 获取用户资料
QC.api('get_user_info').success((res: UserInfo) => {
console.log('获取用户资料', res);
userinfo.value = res;
});
}
const userinfo = ref<UserInfo>();
最后根据获取到的数据通过父传子传递给子组件,让子组件对页面进行动态数据渲染。
多状态登录
有账号未绑定
实现思路
- 进行手机号码校验
- 验证码按钮倒计时效果(可先放放)
- 进行短信验证码发送 (🚨必须调用接口)
- 进行绑定,完成后把当前用户数据存入
Pinia,跳转到首页
手机号码校验
手机号码校验可以使用正则表达式进行校验,触发事件函数时对手机号进行校验,失败则终止函数返回提示,成功则发送获取验证码请求。
- 声明
reactive对象,存储手机号mobile和验证码code.<script> // 准备表单数据 const form = reactive({ mobile: "13535337057", code: "", }); </script> - 在组件中双向绑定数据。
<input v-model="form.mobile" class="input" type="text" placeholder="绑定的手机号" /> <input v-model="form.code" class="input" type="text" placeholder="短信验证码" /> - 点击发送验证码按钮校验手机号
<script>
const sendCode = () => {
if (!/^1[3-9]\d{9}$/.test(form.mobile)) {
return message({ type: 'error', text: '手机号有误' });
}
message({ type: 'success', text: '已发送验证码' });
};
</script>
<span @click="sendCode" class="code">发送验证码</span>
发送验证码业务
注意:
这个发送的动作必须要有!也就是接口必须要调用才可以绑定。
接口文档:三方登录_发送已有账号短信
Path: /login/social/code
Method: GET
请求参数
Query
| 参数名称 | 是否必须 | 示例 | 备注 |
|---|---|---|---|
| mobile | 否 | 13211112222 | 手机号 |
点击发送验证码按钮校验手机号是否正确,如果正确,则调用接口发送数据,获取验证码。
-
在
pinia中封装一个获取验证码接口的函数import { http } from '@/utils/request'; import { defineStore } from 'pinia'; const useLoginStore = defineStore('member', { actions: { async loginSocialCode(mobile: string) { await http('GET', '/login/social/code', { mobile }); }, }, }); -
引入接口函数,点击按钮后调用
const { member } = useStore(); const sendCode = async () => { // 1. 进行手机号格式校验 if (!/^1[3-9]\d{9}$/.test(form.mobile)) { // 校验不通过,提醒用户 return message({ type: "warn", text: "手机号码格式错误~" }); } // 2. 调用获取验证码接口 await member.loginSocialCode(form.mobile); // 调用成功,用户提示 message({ type: "success", text: "验证码已发送~" }); };
unionId 参数父传子
实现验证码获取后QQ登录最后一个需要的参数是 unionId ,这个前面登陆实现步骤已经获取到,可以声明一个变量存储,再传递给子组件使用。
<script setup lang="ts">
const userInfo = ref<UserInfo>();
const unionId = ref("");
// 1. 检查用户是否已登录
if (QC.Login.check()) {
// 2. 获取 QQ 用户唯一标识 openId
QC.Login.getMe((openId) => {
unionId.value = openId;
});
// 3. 获取用户资料
QC.api("get_user_info").success((res: UserInfo) => {
userInfo.value = res;
});
}
</script>
<template>
<section class="container">
<div class="tab-content">
<KeepAlive>
<component
:userInfo="userInfo"
:unionId="unionId"
:is="isBind ? CallbackBind : CallbackRegister"
/>
</KeepAlive>
</div>
</section>
</template>
子组件 Props 获取。
<script setup lang="ts">
interface Props {
userInfo: UserInfo;
unionId: string;
}
</script>
QQ登录并绑定手机号
接口文档:三方登录_账号绑定
Path: /login/social/bind
Method: POST
请求参数
Body
| 名称 | 类型 | 是否必须 | 默认值 | 备注 | 其他信息 |
|---|---|---|---|---|---|
| unionId | string | 必须 | 三方标识 | QQ登录后的 openId | |
| mobile | string | 必须 | 手机号 | ||
| code | string | 必须 | 验证码 |
现在三个参数我们都已经拿到了,可以实现登录业务了,先封装一个接口函数,用于发送请求。
async loginSocialBind(data: {
unionId: string;
mobile: string;
code: string;
}) {
const res = await http<Profile>('POST', '/login/social/bind', data);
// console.log(res);
this.profile = res.data.result;
const { target = '/' } = router.currentRoute.value.query;
router.push(target as string);
message({ type: 'success', text: '登录成功~' });
},
请求成功后要保存数据,跳转页面。
组件中为绑定按钮添加点击事件,点击事件处理函数中调用函数发送请求。
const loginFn = () => {
member.loginSocialBind({
mobile: form.mobile,
code: form.code,
unionId: unionId,
});
};
有账号已绑定
接口描述:三方直接登录
Path: /login/social
Method: POST
请求参数
Body
| 名称 | 类型 | 是否必须 | 备注 |
|---|---|---|---|
| unionId | string | 必须 | 三方标识 |
| source | integer | 必须 | 注册来源 注册来源,1为pc,2为webapp,3为微信小程序,4为Android,5为ios,6为qq,7为微信 |
查看文档,已绑定的事件需要两个参数,一个是 unionId ,在登录实现中可获取到,一个是注册来源,这里我们是QQ注册,因此选6.
先封装一个函数。
async loginsocial(data: { unionId: string; source: number }) {
const res = await http<Profile>('post', '/login/social', data);
console.log(res);
this.profile = res.data.result;
const { target = '/' } = router.currentRoute.value.query;
router.push(target as string);
message({ type: 'success', text: '登录成功~' });
},
在登录实现获取到用户id后立即调用接口发请求。
// 1. 检查用户是否已登录
if (QC.Login.check()) {
// 2. 获取 QQ 用户唯一标识 openId
QC.Login.getMe((openId) => {
console.log('openId', openId);
unionId.value = openId;
// 🚨 获取 openId 后,尝试直接登录
member.loginsocial({ unionId: openId, source: 6 });
});
// 3. 获取用户资料
QC.api('get_user_info').success((res: UserInfo) => {
console.log('获取用户资料', res);
userinfo.value = res;
});
}
优化
注册来源我们可以注册成一个枚举,用枚举来实现语义化调用。枚举通过关键字 enum 注册。枚举有自增的属性,只要第一个赋值为1,后续会累赠。
export enum loginScoure {
PC = 1,
WebApp,
MiniProgram,
Android,
IOS,
QQ,
WeChat,
}
现在可以调用这个对象点语法获取QQ的注册来源,等价于6