【愚公系列】2022年01月 Django商城项目13-登录界面-QQ登录功能实现

举报
愚公搬代码 发表于 2022/01/17 23:34:04 2022/01/17
【摘要】 前言 1.QQ互联开发者申请若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现相关连接:https://connect.qq.com/第一步:首先使用qq登录第二步:注册个人应用注册成功后如下 2.QQ互联应用申请成为QQ互联开发者后,还需创建应用,即获取本项目对应与QQ互联的应用ID。相关连接:https://connect.qq.com/manage.html#/appcre...

前言

1.QQ互联开发者申请

若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现

相关连接:https://connect.qq.com/

第一步:首先使用qq登录
在这里插入图片描述
第二步:注册个人应用
在这里插入图片描述
注册成功后如下
在这里插入图片描述

2.QQ互联应用申请

成为QQ互联开发者后,还需创建应用,即获取本项目对应与QQ互联的应用ID。

相关连接:https://connect.qq.com/manage.html#/appcreate/web

在这里插入图片描述
在这里插入图片描述

3.网站对接QQ登录

QQ互联提供有开发文档,帮助开发者实现QQ登录。

相关连接:http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0

一、django实际对接流程

1.创建抽象模型类

from django.db import models

class BaseModel(models.Model):
    """为模型类补充字段"""

    create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    class Meta:
        abstract = True  # 说明是抽象模型类, 用于继承使用,数据库迁移时不会创建BaseModel的表

2.创建QQ用户模型类

from django.db import models
from utils.models import BaseModel

class OAuthQQUser(BaseModel):
    """QQ登录用户数据"""
    # ForeignKey 我们使用了 其他子应用的模型
    # 我们采用 '子应用名.模型类名'
    user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户')
    openid = models.CharField(max_length=64, verbose_name='openid', db_index=True)

    class Meta:
        db_table = 'tb_oauth_qq'
        verbose_name = 'QQ登录用户数据'
        verbose_name_plural = verbose_name

3.注册应用

# Application references
# https://docs.djangoproject.com/en/2.1/ref/settings/#std:setting-INSTALLED_APPS
INSTALLED_APPS = [
    'app',
    # Add your apps here to enable them
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app.users',
    'app.oauth',
]

4.配置QQ登录信息

# QQ登陆相关的
QQ_CLIENT_ID = '填写自己的'

QQ_CLIENT_SECRET = '填写自己的'

QQ_REDIRECT_URI = '填写自己的'

5.登录返回的URL地址和回调信息

from django.shortcuts import render
from QQLoginTool.QQtool import OAuthQQ
from DJ_MeiDuo import settings
from django import http
from django.views import View
# Create your views here.

class OauthQQURLView(View):

    def get(self,request):

        #1.创建实例对象
        state = 'test'
        qqoauth = OAuthQQ(
            client_secret=settings.QQ_CLIENT_SECRET,
            client_id=settings.QQ_CLIENT_ID,
            redirect_uri=settings.QQ_REDIRECT_URI,
            state=state
        )

        #2.调用方法
        login_url = qqoauth.get_qq_url()

        return http.JsonResponse({'login_url':login_url})

class OauthQQUserView(View):

    def get(self,request):

        # 1.获取code
        code = request.GET.get('code')
        if code is None:
            return render(request,'oauth_callback.html',context={'errmsg':'没有获取到指定参数'})
        # 2. 通过读取文档将code转换为token
        qqoauth = OAuthQQ(
            client_secret=settings.QQ_CLIENT_SECRET,
            client_id=settings.QQ_CLIENT_ID,
            redirect_uri=settings.QQ_REDIRECT_URI
        )

        token = qqoauth.get_access_token(code)

        #3.通过token换取openid
        openid = qqoauth.get_open_id(token)

        # 4. 我们需要根据 openid 进行数据的查询
        try:
            qquser = OAuthQQUser.objects.get(openid=openid)
        except OAuthQQUser.DoesNotExist:
            # 如果没有同样的openid,则说明用户没有绑定过

            # 对openid进行一个加密的处理

            openid_access_token = generate_access_token(openid)

            return render(request,'oauth_callback.html',context={'openid_access_token':openid_access_token})
        else:
            # 如果有同样的openid,则说明用户绑定过
            # 则直接登陆

            response = redirect(reverse('contents:index'))

            #1. 设置登陆状态
            login(request,qquser.user)

            #2.设置cookie信息
            response.set_cookie('username',qquser.user.username,max_age=14*24*3600)

            return response
        # return render(request,'oauth_callback.html')

    def post(self,request):
        # 1.先接收数据
        data = request.POST
        # 2.获取数据
        mobile = data.get('mobile')
        password = data.get('pwd')
        sms_code = data.get('sms_code')
        access_token = data.get('access_token')

        # 3.验证数据
        # 省略

        # 4.access_token( 加密之后的openid)解密
        openid = check_access_token(access_token)
        # 5.根据手机号进行用户信息的判断
        try:
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            #     如果此手机号之前没有注册过,则重新创建用户
            user = User.objects.create_user(
                username=mobile,
                password=password,
                mobile=mobile
            )

        else:
            #     如果此手机号之前注册过,绑定前要验证密码
            if not user.check_password(password):
                return http.HttpResponseBadRequest('密码错误')

        # 则和用户进行绑定,
        qquser = OAuthQQUser.objects.create(
            user=user,
            openid=openid
        )
        # 6.设置登陆的状态
        login(request,user)
        # 7.设置cookie信息
        response = redirect(reverse('contents:index'))
        response.set_cookie('username',user.username,max_age=14*24*3600)
        # 8.跳转指定页面
        return response

加解密方法的封装

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

from app.oauth.constants import OPENID_TOKEN_EXPIRE_TIME
from DJ_MeiDuo import settings
def generate_access_token(openid):

    #1. 创建实例对象
    s = Serializer(secret_key=settings.SECRET_KEY,expires_in=OPENID_TOKEN_EXPIRE_TIME)
    #2.组织数据
    data = {
        'openid':openid
    }
    #3.加密处理
    token = s.dumps(data)
    #4. 返回
    return token.decode()


def check_access_token(token):

    #1.创建实例对象
    s = Serializer(secret_key=settings.SECRET_KEY, expires_in=OPENID_TOKEN_EXPIRE_TIME)
    #2. 解密数据
    result = s.loads(token)
    # script = {'openid':'xxxx'}
    #3.返回数据
    return result['openid']

在这里插入图片描述

6.回调页面逻辑功能实现

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>小徐商城-绑定用户</title>
    <link rel="stylesheet" type="text/css" href="{{ static('css/reset.css') }}" />
    <link rel="stylesheet" type="text/css" href="{{ static('css/main.css') }}" />
    <script src="https://cdn.staticfile.org/vue/2.4.2/vue.min.js"></script>
    <script src="https://cdn.staticfile.org/axios/0.18.0/axios.min.js"></script>
</head>
<body>
    <div id="app" v-cloak>
        <div>
            <div class="register_con">
                <div class="l_con fl">
                    <a href="index.html" class="reg_logo"><img src="{{ static('images/logo.png') }}"></a>
                    <div class="reg_slogan">商品美 · 种类多 · 欢迎光临</div>
                    <div class="reg_banner"></div>
                </div>

                <div class="r_con fr">
                    <div class="reg_title clearfix">
                        <h1>绑定用户</h1>
                    </div>
                    <div class="reg_form clearfix">
                        <form id="reg_form" method="post" @submit="on_submit">
                            {{ csrf_input }}
                            <input type="hidden" name="access_token" value="{{ openid_access_token }}">
                            <ul>
                                <li>
                                    <label>手机号:</label>
                                    <input type="text" name="mobile" id="phone" @blur="check_mobile" v-model="mobile">
                                    <span class="error_tip" v-show="error_mobile">[[error_mobile_message]]</span>
                                </li>
                                <li>
                                    <label>密码:</label>
                                    <input type="password" name="pwd" id="pwd" @blur="check_password" v-model="password">
                                    <span class="error_tip" v-show="error_password">请输入8-20位密码</span>
                                </li>
                                <li>
                                    <label>图形验证码:</label>
                                    <input type="text" name="pic_code" id="pic_code" class="msg_input" v-model="image_code">
                                    <img :src="image_code_url" @click="generate_image_code" alt="图形验证码" class="pic_code">
                                    <span class="error_tip" v-show="error_image_code">请填写图形验证码</span>
                                </li>
                                <li>
                                    <label>短信验证码:</label>
                                    <input type="text" name="sms_code" id="msg_code" class="msg_input" v-model="sms_code" @blur="check_sms_code">
                                    <a href="javascript:;" class="get_msg_code" @click="send_sms_code">获取短信验证码</a>
                                    <span class="error_tip" v-show="error_sms_code">请填写短信验证码</span>
                                </li>
                                <li class="reg_sub">
                                    <input type="submit" value="保 存" name="">
                                </li>
                            </ul>
                        </form>
                    </div>
                </div>
            </div>

            <div class="footer no-mp">
                <div class="foot_link">
                    <a href="#">关于我们</a>
                    <span>|</span>
                    <a href="#">联系我们</a>
                    <span>|</span>
                    <a href="#">招聘人才</a>
                    <span>|</span>
                    <a href="#">友情链接</a>
                </div>
                <p>CopyRight © 2022 福建小徐网络科技有限公司 All Rights Reserved</p>
                <p>电话:13960699696    闽ICP备*******8</p>
            </div>
        </div>
    </div>
    <script type="text/javascript" src="{{ static('js/host.js') }}"></script>
    <script type="text/javascript" src="{{ static('js/common.js') }}"></script>
    <script type="text/javascript" src="{{ static('js/oauth_callback.js') }}"></script>
</body>
</html>
let vm = new Vue({
	el: '#app',
    delimiters: ['[[', ']]'],
	data: {
		mobile: '',
		password: '',
		image_code: '',
		sms_code: '',

		error_mobile: false,
		error_password: false,
		error_image_code: false,
		error_sms_code: false,

		error_mobile_message: '',
		error_image_code_message: '',
		error_sms_code_message: '',
		error_password_message:'',
		
		uuid: '',
		image_code_url: '',
		sms_code_tip: '获取短信验证码',
		sending_flag: false,
	},
	mounted(){
		// 界面获取图形验证码
		this.generate_image_code();
	},
	methods: {
		// 生成图形验证码的请求地址
		generate_image_code(){
			// 生成一个编号 : 严格一点的使用uuid保证编号唯一, 不是很严谨的情况下,也可以使用时间戳
			this.uuid = generateUUID();
			// 设置页面中图形验证码img标签的src属性
			this.image_code_url = "/image_codes/" + this.uuid + "/";
		},
		// 检查手机号
		check_mobile(){
			let re = /^1[3-9]\d{9}$/;
			if(re.test(this.mobile)) {
				this.error_mobile = false;
			} else {
				this.error_mobile_message = '您输入的手机号格式不正确';
				this.error_mobile = true;
			}
		},
		// 检查密码
		check_password(){
			let re = /^[0-9A-Za-z]{8,20}$/;
			if (re.test(this.password)) {
				this.error_password = false;
			} else {
				this.error_password = true;
			}
		},
		// 检查图片验证码
		check_image_code(){
			if(!this.image_code) {
				this.error_image_code_message = '请填写图片验证码';
				this.error_image_code = true;
			} else {
				this.error_image_code = false;
			}
		},
		// 检查短信验证码
		check_sms_code(){
			if(!this.sms_code){
				this.error_sms_code_message = '请填写短信验证码';
				this.error_sms_code = true;
			} else {
				this.error_sms_code = false;
			}
		},
		// 发送手机短信验证码
		send_sms_code(){
			if (this.sending_flag == true) {
				return;
			}
			this.sending_flag = true;

			// 校验参数,保证输入框有数据填写
			this.check_mobile();
			this.check_image_code();

			if (this.error_mobile == true || this.error_image_code == true) {
				this.sending_flag = false;
				return;
			}

			// 向后端接口发送请求,让后端发送短信验证码
			let url = '/sms_codes/' + this.mobile + '/?image_code=' + this.image_code+'&image_code_id='+ this.uuid;
			axios.get(url, {
				responseType: 'json'
			})
				.then(response => {
					// 表示后端发送短信成功
					if (response.data.code == '0') {
						// 倒计时60秒,60秒后允许用户再次点击发送短信验证码的按钮
						let num = 60;
						// 设置一个计时器
						let t = setInterval(() => {
							if (num == 1) {
								// 如果计时器到最后, 清除计时器对象
								clearInterval(t);
								// 将点击获取验证码的按钮展示的文本回复成原始文本
								this.sms_code_tip = '获取短信验证码';
								// 将点击按钮的onclick事件函数恢复回去
								this.sending_flag = false;
							} else {
								num -= 1;
								// 展示倒计时信息
								this.sms_code_tip = num + '秒';
							}
						}, 1000, 60)
					} else {
						if (response.data.code == '4001') {
							this.error_image_code_message = response.data.errmsg;
							this.error_image_code = true;
                        } else { // 4002
							this.error_sms_code_message = response.data.errmsg;
							this.error_sms_code = true;
						}
						this.generate_image_code();
						this.sending_flag = false;
					}
				})
				.catch(error => {
					console.log(error.response);
					this.sending_flag = false;
				})
		},
		// 绑定openid
		on_submit(){
			this.check_mobile();
			this.check_password();
			this.check_sms_code();

			if(this.error_mobile == true || this.error_password == true || this.error_sms_code == true) {
				// 不满足条件:禁用表单
				window.event.returnValue = false
			}
		}
	}
});

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200