BOYYANG/1/blog/compressed/pic_20250330172733_3gbizhi-6d23cfbe55a814de257a718de260c392

QQ互联(QQ登录),前后端分离对接思路

作者: boyyang
分类: 前端开发
发布: 2025-03-30 09:26:01
更新: 2025-04-03 11:53:56
浏览: 79

   如何接入QQ登录功能,我在网上查询了很多实现方案,但是前后端分离的情况。没有看到有实现的方案,有肯定是有的,可能没有找到。

   接下来大概讲一下前后端分离的情况下,接入QQ登录功能的大概思路。在整个接入的流程中最重要的两个东西是,appid和appkey( 申请地址 ),

1. 申请基本信息填写

资料填写.png

   下拉选择根据网站来或者选里面的其它选项,网站名称和简介貌似没有啥影响,点击创建应用进入下一步。

资料填写2.png

   这一步的的填写就比较关键了,网站地址也就是你部署项目的域名,回调地址(我使用的是前端页面的地址),网站地址以及回调地址必须为同一个域名下的,比如说你的网站地址为:https://www.boyyang.cn 那么你的回调地址必须也是该域名下的地址,比如:https://www.boyyang.cn/loginBack

   提供方如果你是个人开发就直接写你名字就行,如果是为公司就填写公司名称,图标按照要求上传就行,网站备案号,也就是你填写的网站地址备案号就行。如果你的域名没有备案,那肯定是不行的。

   什么是回调地址呢?回调地址就是当用户授权登录成功后,QQ互联的那个页面会跳转到你所填写的回调地址页面。

qq登录.png

   当你在这个页面授权登录成功后,他会打开你所填写的回调地址页面,同时他会拼接上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这类的东西,然后拿这些东西取换取用户信息,拿到了这些信息,怎么处理就是自己的事情了。


#vue3
#go
#javascript
#typescript