
QQ互联(QQ登录),前后端分离对接思路
如何接入QQ登录功能,我在网上查询了很多实现方案,但是前后端分离的情况。没有看到有实现的方案,有肯定是有的,可能没有找到。
接下来大概讲一下前后端分离的情况下,接入QQ登录功能的大概思路。在整个接入的流程中最重要的两个东西是,appid和appkey( 申请地址 ),
1. 申请基本信息填写
下拉选择根据网站来或者选里面的其它选项,网站名称和简介貌似没有啥影响,点击创建应用进入下一步。
这一步的的填写就比较关键了,网站地址也就是你部署项目的域名,回调地址(我使用的是前端页面的地址),网站地址以及回调地址必须为同一个域名下的,比如说你的网站地址为:https://www.boyyang.cn 那么你的回调地址必须也是该域名下的地址,比如:https://www.boyyang.cn/loginBack
提供方如果你是个人开发就直接写你名字就行,如果是为公司就填写公司名称,图标按照要求上传就行,网站备案号,也就是你填写的网站地址备案号就行。如果你的域名没有备案,那肯定是不行的。
什么是回调地址呢?回调地址就是当用户授权登录成功后,QQ互联的那个页面会跳转到你所填写的回调地址页面。
当你在这个页面授权登录成功后,他会打开你所填写的回调地址页面,同时他会拼接上code参数。前端获取到code后,就可以将这个code通过接口传递给后端,后端通过这个code进行一系列操作便可以获取到用户端一些基本信息,比如说用户名称,用户头像,性别等等。
在填写完基本信息后,你的应用会处于审核状态,这个时候你就可以通过点击查看获取到appid和appkey这2个重要的参数了。
2.前端页面准备
前端得准备2个页面,第一个页面就是你登录按钮放哪儿,这个按钮最好放你网站首页比较显眼的位置,因为上一步填写成功后,QQ互联的审核员会访问你提供的网站地址进行访问,如过你的页面打不开或者没有QQ登录按钮审核不会给通过的。第二个页面便是你的回调地址页面。
点击QQ登录按钮的代码如下:
const qqLogin = () => {
const state = JSON.stringify({path: route.path, query: route.query})
window.sessionStorage.setItem('state', state)
const url = `https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=${APP_ID}&state=${state}&redirect_uri=${encodeURIComponent(REDIRECTURI)}`
window.open(url, '_blank')
}
其中state可以根据自己的需求传递,最好是唯一的,这个值是必须传递的,你传递的是啥,在授权成功后,他会将该值传回给你,这样你就可以知道这个链接是否被篡改过的。
回调页面的大致代码如下:
<script lang="ts" setup>
import {useRoute, useRouter} from 'vue-router'
import {useUserStore} from '@/store/userStore'
import {signInByQQ} from '~/service/login'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const getCode = () => {
const {code, state} = route.query
if (state === window.sessionStorage.getItem('state')) {
signInByQQ({code: code as string}).then((res) => {
if (res.code === 1) {
userStore.setUserInfo(res.data.user_info)
userStore.setToken(res.data.token)
const routeParam = JSON.parse(state as string)
router.replace({path: routeParam.path, query: routeParam.query})
}
})
}
}
onMounted(() => {
getCode()
})
</script>
在这个页面你可以拿到code以及state的值,将code传递给后端,接下来就是后端的工作了。后端通过前端获取到的code等参数获取用户信息等相关操作。在signInByQQ这个接口其实就可以直接返回用户信息,token等相关信息了。
3.后端
后端做得操作相对来说比较多,由于我使用的go-zero作为后端框架,所以就讲一些大概的过程。
在前端拿到code后通过signInByQQ这个接口将code传递给了后端,后端拿到这个后可以通过code去获取AccessToken 代码片段如下:
type AccessTokenInfo struct {
AccessToken string `json:"access_token"`
ExpiresIn string `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
}
func AccessToken(code string, redirectURI string) (accessToken *AccessTokenInfo, err error) {
params := url.Values{}
params.Add("grant_type", "authorization_code")
params.Add("client_id", AppId)
params.Add("client_secret", AppKey)
params.Add("code", code)
params.Add("fmt", "json")
str := fmt.Sprintf("%s&redirect_uri=%s", params.Encode(), redirectURI)
loginURL := fmt.Sprintf("%s?%s", "https://graph.qq.com/oauth2.0/token", str)
response, err := http.Get(loginURL)
if err != nil {
return nil, err
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(response.Body)
var accessTokenInfo AccessTokenInfo
err = json.Unmarshal(body, &accessTokenInfo)
if err != nil {
return nil, err
}
return &accessTokenInfo, nil
}
通过code换取到token后下一步便是通过token换取openid了,代码片段如下:
type OpenIdInfo struct {
OpenId string `json:"openid"`
ClientId string `json:"client_Id"`
}
func OpenId(accessToken string) (openIdInfo *OpenIdInfo, err error) {
resp, err := http.Get(fmt.Sprintf("%s?access_token=%s&fmt=json", "https://graph.qq.com/oauth2.0/me", accessToken))
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &openIdInfo)
if err != nil {
return nil, err
}
return openIdInfo, nil
}
在获取到token以及openid后,便可以通过这2个参数去获取用户的基本信息了,代码片段如下:
type QQUserInfo struct {
Nickname string `json:"nickname"`
Avatar string `json:"figureurl_qq_1"`
}
func UserInfo(accessToken string, openid string) (userInfo *QQUserInfo, err error) {
params := url.Values{}
params.Add("access_token", accessToken)
params.Add("openid", openid)
params.Add("oauth_consumer_key", AppId)
uri := fmt.Sprintf("https://graph.qq.com/user/get_user_info?%s", params.Encode())
resp, err := http.Get(uri)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &userInfo)
if err != nil {
return nil, err
}
return userInfo, nil
}
这里我只获取了用户昵称以及头像,除此之外以为还能获取到一些其它的信息:
参数说明 | 描述 |
---|---|
ret | 返回码 |
msg | 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码 |
is_lost | 判断是否有数据丢失。如果应用不使用cache,不需要关心此参数。0或者不返回:没有数据丢失,可以缓存。1:有部分数据丢失或错误,不要缓存 |
nickname | 用户在QQ空间的昵称。 |
figureurl | 大小为30×30像素的QQ空间头像URL。 |
figureurl_1 | 大小为50×50像素的QQ空间头像URL。 |
figureurl_2 | 大小为100×100像素的QQ空间头像URL。 |
figureurl_qq_1 | 大小为40×40像素的QQ头像URL。 |
figureurl_qq_2 | 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100x100的头像,但40x40像素则是一定会有。 |
gender | 性别。 如果获取不到则默认返回"男" |
gender_type | 性别类型。默认返回2 |
province | 省 |
city | 市 |
year | 年 |
constellation | 星座 |
is_yellow_vip | 标识用户是否为黄钻用户 |
yellow_vip_level | 黄钻等级 |
is_yellow_year_vip | 是否为年费黄钻用户 |
拿到这些信息后,后端便可以通过这些信息写入数据库了,然后下发token等信息返回给前端。整个流程也就结束了。
3.一些注意项
QQ登录接入过程中是不好调试的,在审核前需要将前端的按钮放出来,并且点击按钮成功跳转到QQ登录的页面,只有这样审核才会通过。也就是说你的网站必须将前置工作做完了,然后线上能访问了,审核才会给你通过。
总结:
在刚刚接触看到那么多东西,脑子一团浆糊,按照文档一步一步下来,其实就是调取QQ互联的接口,按照接口顺序获取参数,比如说code,token这类的东西,然后拿这些东西取换取用户信息,拿到了这些信息,怎么处理就是自己的事情了。