6.qq第三方登陆

QQ登录

OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。oAuth是Open Authorization的简写。

我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录荏苒网。

要开发第三方登录功能,必须要成为QQ互联开发者后,创建应用,即获取当前项目与QQ互联的应用ID,创建应用的方法参考链接http://wiki.connect.qq.com/__trashed-2

QQ第三方登录的实现流程

三种情况:

  1. 用户没有当前网站的账号,第一次使用qq登录(需要注册新的账户信息)
  2. 用户有当前网站的账号,第一次使用qq登录(直接登录)
  3. 用户有当前网站的账号,不是第一次使用qq登录(账号绑定qq)

创建模型类

1.创建一个新的应用oauth,用来实现QQ第三方认证登录。

cd renranapi/apps
python ../../manage.py startapp oauth

配置文件 settings/dev.py,添加子应用

INSTALLED_APPS = [
    'users',
    'home',
    'oauth',
]

配置总路由renranapi/urls.py

urlpatterns = [
    path('oauth/', include('oauth.urls')),
]

创建子路由文件aouth/urls.py

from django.urls import path
from . import views

urlpatterns = [
    # path('register/', views.RegisterView.as_view()),
]

2.创建模型类:

在oauth/models.py中定义QQ身份(openid)与用户模型类User的关联关系

from django.db import models

# Create your models here.
from django.db import models
from renranapi.utils.models import BaseModel
from users.models import User
class OAuthUser(BaseModel):
    """
    登录用户数据
    """
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户')
    openid = models.CharField(max_length=64, verbose_name='openid', db_index=True)
    access_token = models.CharField(max_length=500,verbose_name="临时访问票据", help_text="有效期:3个月")
    refresh_token = models.CharField(max_length=500,verbose_name="刷新访问票据的token", help_text="当access_token过期以后,可以使用refresh_token来重新获取新的access_token")
    class Meta:
        db_table = 'rr_oauth_qq'
        verbose_name = 'QQ登录用户数据'
        verbose_name_plural = verbose_name

进行数据库迁移

python manage.py makemigrations
python manage.py migrate

urllib使用说明

在后端接口中,我们需要向QQ服务器发送请求,查询用户的QQ信息,Python提供了标准模块urllib可以帮助我们发送http请求。
引入方法:
    from urllib.request import urlopen
    from urllib.parse import urlencode, parse_qs
使用:
	1.urlencode(query)
		将query字典转换为url路径中的查询字符串 -- a=1&b=2
    2.parse_qs(res)    
    	将res查询字符串格式数据转换为python的字典
    3.urlopen(url, data=None)
    	发送http请求,如果data为None,发送GET请求,如果data不为None,发送POST请求
    	返回response响应对象,通过read()读取响应数据,响应数据为bytes类型,需decode()

代码实现

服务端

  1. 在settings/dev.py中增加QQ登录相关配置
# QQ登录参数
QQ_APP_ID = '101403367'
QQ_APP_KEY = '93112df14c10d6fde74baa62f5de95ab'
QQ_REDIRECT_URL = 'http://www.moluo.net:8080/oauth_callback.html'
QQ_STATE = "/" # 用于保存登录成功后的跳转页面路径
  1. 在oauth子应用下,创建utils.py文件,编写QQ登录的辅助类,方便以后项目做qq登录时使用
from urllib.parse import urlencode, parse_qs
from urllib.request import urlopen
from django.conf import settings
import logging

logger = logging.getLogger('django')

class OAuthQQ(object):
    """
    QQ认证辅助工具类
    """
    def __init__(self, app_id=None, app_key=None, redirect_uri=None, state=None):
        self.app_id = app_id or settings.QQ_APP_ID
        self.app_key = app_key or settings.QQ_APP_KEY
        self.redirect_url = redirect_uri or settings.QQ_REDIRECT_URL
        self.state = state or settings.QQ_STATE  # 用于保存登录成功后的跳转页面路径

    # 生成QQ第三方登录的页面链接
    def get_auth_url(self):

        # 路径携带参数 -- 官方文档里有
        params = {
            'response_type':'code', # 授权类型,此值固定为“code”
            'client_id':self.app_id, # 申请QQ登录成功后,分配给应用的appid。
            'redirect_uri':self.redirect_url,# 成功授权后的回调地址,需要将url进行URLEncode
            'state':self.state, # 用于第三方应用防止CSRF攻击,可以是随机值
        }
        # 把字典数据加工成网址的参数形式:a=1&b=2
        url_params = urlencode(params)
        url_code = 'https://graph.qq.com/oauth2.0/authorize'
        qq_url = f'{url_code}?{url_params}' # 请求网址路径拼接

        return qq_url

3.路由oauth/urls.py

from django.urls import path
from . import views
urlpatterns = [
    path('qq/code/', views.QQCodeView.as_view()),
]

4.视图代码oauth/views.py:

from rest_framework.views import APIView
from .utils import OAuthQQ
from rest_framework.response import Response
# 获取qq服务器授权码code
class QQCodeView(APIView):
    
    def get(self,request):
        # 实例化QQ第三方登录的辅助类对象
        oauth_qq_obj = OAuthQQ()
        # 获取向qq服务器授权码code的地址
        qq_url = oauth_qq_obj.get_auth_url()

        return Response({'url':qq_url})

客户端请求获取QQ第三方登录地址

修改Login.vue,在methods中增加qq_login方法

<template>

<!-- 更多登录方式 -->
<div class="more-sign">
    <h6>社交帐号登录</h6>
    <ul>
        <li id="weibo-link-wrap" class="">
            <el-link class="weibo" id="weibo-link">
                <i class="iconfont ic-weibo"></i>
    </el-link>
    </li>
        <li><el-link id="weixin" class="weixin" target="_blank" href=""><i class="iconfont ic-wechat"></i></el-link>
    </li>
        <li><el-link @click="qq_login" id="qq" class="qq" target="_blank" ><i class="iconfont ic-qq_connect"></i></el-link></li>
    </ul>
    </div>
    </div>

</template>

<script>
    export default {
        name: "Login",
        data(){
            return {
                username:"",
                password:"",
                remember_me: false,
            }
        },
        methods:{
            qq_login(){
                this.$axios.get(`${this.$settings.host}/oauth/qq/code`)
                    .then((res)=>{
                    let url = res.data.url; // 获取后台发送的网址
                    location.href = url;    // 网页跳转
                }).catch((error)=>{
                    this.$message.error('网络错误!无法使用QQ第三方登陆!')
                })
            },
</script>

然后用户此时就可以在页面中通过点击跳转到QQ第三方登录页面了。

BUG

如果实现上面代码以后QQ页面出现如下错误信息:

对不起,该网站尚未开通QQ帐号登录(错误码:100008)

则表示当前站点应用没有通过审核。这表示我们的代码没有问题了,但是注册的应用是没通过或者审核中的。

解决方案:使用以下QQ登录应用进行测试开发。

# 1. 在settings/dev.py文件中修改QQ第三方登录配置。
	# QQ登录参数
    QQ_APP_ID = '101403367'
    QQ_APP_KEY = '93112df14c10d6fde74baa62f5de95ab'
    QQ_REDIRECT_URL = 'http://www.moluo.net:8080/oauth_callback.html'
    QQ_STATE = "/" # 用于保存登录成功后的跳转页面路径
    
# 2. 在/etc/hosts/中添加如下配置信息:
    # sudo vim /etc/hosts
    127.0.0.1   www.moluo.net

# 3. 在客户端项目中临时修改config/index.js中的host域名为:www.moluo.net
#    并重启客户端项目


# 4. 在api服务端项目的配置文件settings/dev.py中,添加www.moluo.net到CORS_ORIGIN_WHITELIST列表中,并重启服务端项目
     # CORS组的配置信息
CORS_ORIGIN_WHITELIST = (
    'http://www.renran.com:8080',
    'http://www.moluo.net:8080',

)
    
    # 5  ALLOWED_HOSTS
    ALLOWED_HOSTS = [
    'api.renran.com', 'www.renran.com', 'www.moluo.net',
]

经过上面的提供的操作,用户在QQ登录成功后,QQ会将用户重定向回我们配置的回调域网址,我们申请QQ登录开发资质时配置的回调地址为:http://www.renran.cn:8080/login/qq_callback或者http://www.moluo.net:8080/oauth_callback.html。接下来,我们要在客户端对提供一个QQCallBack组件页面,地址绑定为oauth_callback.html

客户端处理回调网址

1.路由router/index.js中绑定路由

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router);

import QQCallBack from "@/components/QQCallBack"

export default new Router({
  mode: "history",
  routes: [
      {
       name:"QQCallBack",
       path:"/oauth_callback.html",// 改成自己注册QQ登录时的地址
       component: QQCallBack,
     },
  ]
})

2.接收地址栏上面的code参数转发到服务端,服务端再次请求QQ服务器提取访问QQ用户信息的access_token。

客户端代码QQCallBack.vue:

<template>

  <div class="QQCallBack">
    <h1>QQ回调地址</h1>
  </div>

</template>

<script>
export default {
  name: "QQCallBack",
  data(){
    return{

    }
  },
  methods:{
    get_user_info(){
      let params = location.search; // 获取qq服务器发过来的路径参数code,把code发给后台
      this.$axios.get(`${this.$settings.host}/oauth/qq/info/${params}`)
      .then((res)=>{
        console.log(res.data)
      }).catch((error)=>{
        this.$message.error('网络错误!无法使用QQ第三方登陆!')
      })
    },
  },
  created() {
    this.get_user_info();
  },
}
</script>
<style scoped>
</style>

服务端代码

3.服务端在uauth/utils.py的OAuthQQ辅助类中新增获取access_token和根据access_token提取openid的方法:

from urllib.request import urlopen
from django.conf import settings
from urllib.parse import urlencode, parse_qs
import logging
import json

logger =logging.getLogger("django")

# 为了在日志中区分在哪出现的异常报错
class OAuthQQError(Exception):
    pass

# 封装QQ第三方登录的辅助类
class OAuthQQ(object):
    # 初始化属性
    def __init__(self, app_id=None, app_key=None, redirect_uri=None, state=None):
        self.app_id = app_id or settings.QQ_LOGIN_INFO.get('QQ_APP_ID')     # 应用ID
        self.app_key = app_key or settings.QQ_LOGIN_INFO.get('QQ_APP_KEY')  # 应用秘钥 
        self.redirect_url = redirect_uri or settings.QQ_LOGIN_INFO.get('QQ_REDIRECT_URL') # 回调域名
        self.state = state or settings.QQ_LOGIN_INFO.get('QQ_STATE')  # 用于保存登录成功后的跳转页面路径

    # 生成QQ第三方登录的页面链接
    def get_auth_url(self):

        # 路径携带参数 -- 官方文档里有
        params = {
            'response_type':'code', # 授权类型,此值固定为“code”
            'client_id':self.app_id, # 申请QQ登录成功后,分配给应用的appid。
            'redirect_uri':self.redirect_url,# 成功授权后的回调地址,需要将url进行URLEncode
            'state':self.state, # 用于第三方应用防止CSRF攻击,可以是随机值
        }
        # 把字典数据加工成网址的参数形式:a=1&b=2
        url_params = urlencode(params)
        url_code = 'https://graph.qq.com/oauth2.0/authorize'
        qq_url = f'{url_code}?{url_params}' # 请求网址路径拼接

        return qq_url

    # 通过授权码获取临时票据access_token和refresh_token
    def get_access_token(self,code):

        # 路径携带参数 -- 官方文档里有
        params = {
            'grant_type': 'authorization_code',
            'client_id': self.app_id,
            'client_secret': self.app_key,
            'redirect_uri': self.redirect_url,
            'code': code,
        }
        # 拼接地址 -- urlencode 把字典转换成网址参数
        url = 'https://graph.qq.com/oauth2.0/token?' + urlencode(params)
        try:
            response = urlopen(url) # 路径下发送get请求
            response_data = response.read().decode() # 获取响应数据
            # parse_qs 把查询字符串格式的内容转换成字典[注意:转换后的字典,值是列表格式]
            data = parse_qs(response_data)
            access_token = data.get('access_token')[0]
            refresh_token = data.get('refresh_token')[0]
        except:
            # 异常捕获,记录日志
            logger.error('code=%s msg=%s' % (data.get('code'), data.get('msg')))
            raise OAuthQQError

        return access_token,refresh_token

    # 根据access_token获取openID
    def get_open_id(self,access_token):
        # 路径拼接
        url = 'https://graph.qq.com/oauth2.0/me?access_token=' + access_token
        try:
            response = urlopen(url) # 路径下发送get请求
            # 获取响应数据json字符串: callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
            response_data = response.read().decode()
            data = json.loads(response_data[10:-4]) # 反序列化获取字典数据
            openid = data.get('openid')
        except:
            logger.error('code=%s msg=%s' % (data.get('code'), data.get('msg')))
            raise OAuthQQError

        return openid

    # 如果access_token临时票据过期,可以根据refresh_token刷新access_token和refresh_token
    def refresh_access_token(self,refresh_token):

        # 路径携带参数 -- 官方文档里有
        params = {
            'grant_type': 'refresh_token',
            'client_id': self.app_id,
            'client_secret': self.app_key,
            'refresh_token': refresh_token,

        }
        # 拼接地址 -- urlencode 把字典转换成网址参数
        url = 'https://graph.qq.com/oauth2.0/token?' + urlencode(params)
        try:
            response = urlopen(url) # 路径下发送get请求
            response_data = response.read().decode() # 获取响应数据
            # parse_qs 把查询字符串格式的内容转换成字典[注意:转换后的字典,值是列表格式]
            data = parse_qs(response_data)
            access_token = data.get('access_token')[0]
            refresh_token = data.get('refresh_token')[0]
        except:
            # 异常捕获,记录日志
            logger.error('code=%s msg=%s' % (data.get('code'), data.get('msg')))
            raise OAuthQQError

        return access_token,refresh_token

    # 根据access_token和openid获取用户信息
    def get_user_info(self, access_token, openid):
        # 路径携带参数 -- 官方文档里有
        params = {
            'access_token': access_token,
            'oauth_consumer_key': self.app_id,
            'openid': openid,
        }
        url = 'https://graph.qq.com/user/get_user_info?' + urlencode(params) # 路径拼接
        try:
            response = urlopen(url) # 路径下发送get请求
            response_data = response.read().decode() #获取响应数据,json格式(里面是字典类型)
            data = json.loads(response_data) # 反序列化
            return data
        except:
            logger.error('code=%s msg=%s' % (data.get('code'), data.get('msg')))
            raise OAuthQQError


4.视图调用辅助类完成获取QQ用户信息的逻辑: oauth/views.py


from rest_framework.views import APIView
from rest_framework.response import Response
from .utils import OAuthQQ,OAuthQQError
from .models import OAuthUser
from users.utils import get_user_object
import json
import re
from django_redis import get_redis_connection
from users.models import User
from django.db import transaction  # 事物
from django.contrib.auth.hashers import make_password # 系统加密密码
from rest_framework import status


# 获取qq服务器授权码code
class QQCodeView(APIView):

    def get(self,request):
        # 实例化QQ第三方登录的辅助类对象
        oauth_qq_obj = OAuthQQ()
        # 获取向qq服务器授权码code的地址
        qq_url = oauth_qq_obj.get_auth_url()

        return Response({'url':qq_url})


class QQInfoView(APIView):

    # 1.用户已经拥有平台账号而且绑定了QQ账号的情况 -- 直接登录
    def get(self,request):
        # 1.获取客户端发来的QQ登录授权码code
        code = request.query_params.get('code')
        state = request.query_params.get('state')
        if not code:
            return Response("QQ登录异常!请重新尝试登录!",status=status.HTTP_400_BAD_REQUEST)

        oauth_qq_obj = OAuthQQ() # 实例化QQ第三方登录的辅助类对象
        try:
            # 2.通过授权码code获取access_token和refresh_token值
            access_token, refresh_token = oauth_qq_obj.get_access_token(code)

            # 3.通过access_token获取openid
            openid = oauth_qq_obj.get_open_id(access_token)

            # 4.通过access_token和openid,可以获取用户信息
            user_info_data = oauth_qq_obj.get_user_info(access_token,openid)

        except OAuthQQError:
            return Response("QQ登录异常!获取授权信息失败!请重新尝试登录!",status=status.HTTP_400_BAD_REQUEST)

        # 1.用户已经拥有平台账号而且绑定了QQ账号的情况 -- 直接登录
        try:
            oauth_qq_user = OAuthUser.objects.get(openid=openid)
            # 查找到对应的用户记录,证明用户创建我们网站的账号并且已经关联了QQ的openID
            user = oauth_qq_user.user

            # 手动生成jwt中的token值 -- 响应用户登录信息
            return self.response_user_login_data(user)

        except OAuthUser.DoesNotExist:
            # 查找不到对应的用户记录,用户属于第一次使用QQ登录
            # 把qq服务相关数据返回客户端,
            res_data = {
                'qq_info':json.dumps({'openid':openid,'access_token':access_token,'refresh_token':refresh_token,})
            }
            return Response(res_data)


    # 2.用户拥有账号而且没有绑定了QQ账号的情况 -- 引导登录 -- 绑定openid
    def put(self,request):
        username = request.data.get('username')
        password = request.data.get('password')
        qq_info = request.data.get('qq_info')
        qq_info_dict = json.loads(qq_info)

        # 验证用户名密码是否正确 --- 创建用户与qq关联表记录
        user = get_user_object(username)
        if not user:
            return Response({'error':'用户名或密码错误!请重新输入'},status=status.HTTP_400_BAD_REQUEST)
        if not user.check_password(password):
            return Response({'error':'用户名或密码错误!请重新输入'},status=status.HTTP_400_BAD_REQUEST)

        # 创建用户与qq关联表记录
        OAuthUser.objects.create(**{
            'user':user,
            'openid':qq_info_dict.get('openid'),
            'access_token':qq_info_dict.get('access_token'),
            'refresh_token':qq_info_dict.get('refresh_token'),
        })

        # 手动生成jwt中的token值 -- 响应用户登录信息
        return self.response_user_login_data(user)


    # 3.用户已经没有平台账号而且没有绑定了QQ账号的情况 -- 引导注册
    def post(self,request):
        nickname = request.data.get('nickname')
        mobile = request.data.get('mobile')
        sms_code = request.data.get('sms_code')
        password = request.data.get('password')
        qq_info = request.data.get('qq_info')
        qq_info_dict = json.loads(qq_info)

        # 1.昵称唯一性校验
        user = get_user_object(nickname)
        if user:
            return Response({'error':'昵称不能重复!'},status=status.HTTP_400_BAD_REQUEST)

        # 2.手机号唯一性校验
        user = get_user_object(mobile)
        if user:
            return Response({'error':'手机号已经被注册!'},status=status.HTTP_400_BAD_REQUEST)

        # 3.手机号格式校验
        if not re.match("^1[3-9]d{9}$", mobile):
            return Response({'error':'手机号格式不正确!'},status=status.HTTP_400_BAD_REQUEST)

        # 4.短信验证码验证
        conn = get_redis_connection('sms_code')
        redis_code = conn.get(f'sms_{mobile}') # redis取出来的是bytes数据,取不到值返回none
        if not redis_code:
            return Response({'error':'短信验证码已经过期!请重新发送!'},status=status.HTTP_400_BAD_REQUEST)
        if sms_code != redis_code.decode():
            return Response({'error':'短信验证码输入有误!'},status=status.HTTP_400_BAD_REQUEST)

        # 5.保存用户数据,并且和openid进行关联,多张表同时操作,开启事物
        with transaction.atomic():
            user = User.objects.create(**{
                'nickname':nickname,
                'mobile':mobile,
                'username':nickname,
                'password':make_password(password),
            })
            # 创建用户与qq关联表记录
            OAuthUser.objects.create(**{
                'user':user,
                'openid':qq_info_dict.get('openid'),
                'access_token':qq_info_dict.get('access_token'),
                'refresh_token':qq_info_dict.get('refresh_token'),
            })

        # 手动生成jwt中的token值 -- 响应用户登录信息
        return self.response_user_login_data(user)


    # 手动生成jwt中的token值 -- 响应用户登录信息
    def response_user_login_data(self,user):

        from rest_framework_jwt.settings import api_settings

        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)

        # 携带想要返回客户端的数据
        res_data = {
            'token': token,
            'id': user.id,
            'username': user.username,
            # 'avatar': user.avatar.url, # 图片路径
            'nickname': user.nickname,
        }

        return Response(res_data)

5.路由oauth/urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path('qq/code/', views.QQCodeView.as_view()),
    path('qq/info/', views.QQInfoView.as_view()),
]

客户端实现

1.用户已经拥有平台账号而且绑定了QQ账号的情况 -- 直接登录

2.用户拥有账号而且没有绑定了QQ账号的情况 -- 引导登录 -- 绑定openid

3.用户已经没有平台账号而且没有绑定了QQ账号的情况 -- 引导注册

routers/settings.js -- 客户端保存客户信息--公共

export default {
  'host': 'http://api.renran.com:8000',
  'captcha_app_id':'2032172811 ',//验证码APPID
  'save_user':function (res,remember_me=false) {
        //判断怎样保存token,客户端保存用户信息
        if(remember_me){
          localStorage.token = res.data.token; //勾选了,长期保存token值到本地
          localStorage.id = res.data.id; //勾选了,长期保存token值到本地
          localStorage.username = res.data.username; //勾选了,长期保存token值到本地
          localStorage.avatar = res.data.avatar; //勾选了,长期保存token值到本地
          localStorage.nickname = res.data.nickname; //勾选了,长期保存token值到本地
          sessionStorage.removeItem('token');//清除临时存储的token值
          sessionStorage.removeItem('id');//清除临时存储的token值
          sessionStorage.removeItem('username');//清除临时存储的token值
          sessionStorage.removeItem('avatar');//清除临时存储的token值
          sessionStorage.removeItem('nickname');//清除临时存储的token值
        }else {//否则反过来
          sessionStorage.token = res.data.token;
          sessionStorage.id = res.data.id;
          sessionStorage.username = res.data.username;
          sessionStorage.avatar = res.data.avatar;
          sessionStorage.nickname = res.data.nickname;
          localStorage.removeItem('token');
          localStorage.removeItem('id');
          localStorage.removeItem('username');
          localStorage.removeItem('avatar');
          localStorage.removeItem('nickname');
        }
  },
}

客户端QQCallBack.vue:

<template>
    <div v-show="show_page">
      <div class="sign">
        <div class="logo"><a href="/"><img src="/static/image/nav-logo.png" alt="Logo"></a></div>
        <div class="main">
          <h4 class="title">
            <div class="normal-title">
                <a :class="status==1?'active':''" @click="status=1">已有账号</a>
                <b>·</b>
                <a :class="status==2?'active':''" @click="status=2">没有账号</a>
            </div>
          </h4>
          <div class="js-sign-in-container" v-if="status==1">
            <form action="" method="post">
                <div class="input-prepend restyle js-normal">
                  <input placeholder="登录账号或手机号或邮箱" type="text" v-model="username">
                  <i class="iconfont ic-user"></i>
                </div>
                <div class="input-prepend">
                  <input placeholder="密码" type="password" v-model="password">
                  <i class="iconfont ic-password"></i>
                </div>
                <div class="forget-btn">
                  <router-link to="/find_password">通过邮箱找回密码?</router-link>
                </div>
                <button class="sign-in-button" type="button" @click.prevent="show_captcha">
                  <span></span>登录
                </button>
            </form>
          </div>
          <div class="js-sign-in-container" v-if="status==2">
            <form class="new_user" id="new_user" action="" accept-charset="UTF-8" method="post">
              <div class="input-prepend restyle">
                  <input placeholder="你的昵称" type="text" value="" v-model="nickname" id="user_nickname">
                <i class="iconfont ic-user"></i>
              </div>
                <div class="input-prepend restyle no-radius js-normal">
                    <input placeholder="手机号" type="tel" v-model="mobile" id="user_mobile_number">
                  <i class="iconfont ic-phonenumber"></i>
                </div>
              <div class="input-prepend restyle no-radius security-up-code js-security-number" v-if="is_show_sms_code">
                  <input type="text" v-model="sms_code" id="sms_code" placeholder="手机验证码">
                <i class="iconfont ic-verify"></i>
                <a tabindex="-1" class="btn-up-resend js-send-code-button"  href="javascript:void(0);" id="send_code" @click.prevent="SendSmsCode">{{sms_code_text}}</a>
              </div>
              <input type="hidden" name="security_number" id="security_number">
              <div class="input-prepend">
                <input placeholder="设置密码" type="password" v-model="password" id="user_password">
                <i class="iconfont ic-password"></i>
              </div>
              <input type="submit" name="commit" value="注册" class="sign-up-button" id="sign_up_btn" @click.prevent="registerHandler">
              <p class="sign-up-msg">点击 “注册” 即表示您同意并愿意遵守荏苒<br> <a target="_blank" href="">用户协议</a> 和 <a target="_blank" href="">隐私政策</a> 。</p>
            </form>
          </div>
        </div>
      </div>
    </div>
</template>

<script>
export default {
  name: "QQCallBack",
  data(){
    return{
      show_page: false,
      status: 1, // 当前用户是否拥有了平台账号
      username: "",
      password: "",
      nickname:"",
      mobile:"",
      sms_code:"",
      sms_code_text:"发送验证码",
      is_show_sms_code:false,
      // openid:"", // 用户的openid
      qq_info:'',
    }
  },
  //监听手机号输入
  watch:{
          mobile(){
            if(/^1[3-9]d{9}$/.test(this.mobile)){//手机号正则校验
              this.is_show_sms_code = true;
            }else{
              this.is_show_sms_code = false;
            }
          }
        },

  methods:{
    //短信验证码
    SendSmsCode(){

      this.$axios.get(`${this.$settings.host}/users/sms_code/`,{
        params:{
          mobile:this.mobile,

        }
      }).then((res)=>{
        // 冷却倒计时效果
        let inter_time = 60
        // 定时器
        let t = setInterval( ()=>{
          this.sms_code_text = `${inter_time}秒后重新发送!`
          inter_time--;
          if (inter_time <= 0){
            clearInterval(t); //清掉定时器
            this.sms_code_text = '发送验证码';

          }
        },1000)

      }).catch((error)=>{
        this.$message.error(error.response.data.error) //可以取出后台试图定义过的错误
      })
    },

    // 3. 用户已经没有平台账号而且没有绑定了QQ账号的情况 -- 引导注册
    registerHandler(){
      this.$axios.post(`${this.$settings.host}/oauth/qq/info/`,{
        nickname:this.nickname,
        mobile:this.mobile,
        sms_code:this.sms_code,
        password:this.password,
        qq_info:this.qq_info,
      }).then((res)=>{
        //如客户端保存用户信息,跳转首页
        this.$settings.save_user(res,this.remember_me)
        this.$router.push('/') //跳转页面
      }).catch((error)=>{
        this.$message.error(error.response.data.error) //可以取出后台试图定义过的错误
      })
    },

    // 2. 用户拥有账号而且没有绑定了QQ账号的情况 -- 引导登录 -- 绑定openid
    // 登录按钮 -- 滑动验证码
    show_captcha(){
      let self = this
      // 直接生成一个验证码对象。--回调函数
      var captcha1 = new TencentCaptcha(`${this.$settings.captcha_app_id}`, function(res) {
      // 滑动成功 向后台发送数据
        self.$axios.get(`${self.$settings.host}/users/check_captcha_data/`,{
          // 回调结果 ret -- 验证结果,0:验证成功。2:用户主动关闭验证码。
          params:{
          randstr:res.randstr,//本次验证的随机串,请求后台接口时需带上。
          ticket:res.ticket, //验证成功的票据,当且仅当 ret = 0 时 ticket 有值。
          }
        }).then((res)=>{
          // 客户端校验用户名和密码是否正确,绑定qq
          self.$axios.put(`${self.$settings.host}/oauth/qq/info/`,{
            // 向后台发送数据
            qq_info:self.qq_info,
            username:self.username,
            password:self.password,
          }).then((res)=>{
            //如客户端保存用户信息,跳转首页
            self.$settings.save_user(res,this.remember_me)
            self.$router.push('/') //跳转页面
          }).catch((error)=>{
            self.$message.error(error.response.data.error) //可以取出后台试图定义过的错误
          })

        }).catch((error)=>{
          self.$message.error("验证码校验错误!");
        })
      });
      captcha1.show(); // 显示验证码
    },

    // 获取qq服务器中转信息
    get_user_info(){
      let params = location.search; // 获取qq服务器发过来的路径参数code,把code发给后台
      this.$axios.get(`${this.$settings.host}/oauth/qq/info/${params}`)
      .then((res)=>{
        console.log(res.data)
        // 判断服务端返回的数据带不带token值
        if (res.data.token){
          // 1.用户已经拥有平台账号而且绑定了QQ账号的情况 -- 直接登录
          //如果有,客户端保存用户信息,不显示中转页面,直接跳转首页
          this.$settings.save_user(res,this.remember_me)
          this.$router.push('/') //跳转页面
        }else{
          //如果没有,显示登录注册页面
          this.qq_info = res.data.qq_info
          this.show_page = true
        }

      }).catch((error)=>{
          this.$message.error(error.response.data.error) //可以取出后台试图定义过的错误
      })
    },
  },
  created() {
    this.get_user_info();
  },
}
</script>

原文地址:https://www.cnblogs.com/jia-shu/p/14589733.html