1、openid是否绑定用户的处理

判断openid是否绑定过用户

使用openid查询该QQ用户是否在商城中绑定过用户。

try:oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:# 如果openid没绑定美多商城用户pass
else:# 如果openid已绑定美多商城用户pass

openid已绑定用户的处理

如果openid已绑定美多商城用户,直接生成状态保持信息,登录成功,并重定向到首页。

try:oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:# 如果openid没绑定商城用户pass
else:# 如果openid已绑定商城用户# 实现状态保持qq_user = oauth_user.userlogin(request, qq_user)# 响应结果next = request.GET.get('state')response = redirect(next)# 登录时用户名写入到cookie,有效期15天response.set_cookie('username', qq_user.username, max_age=3600 * 24 * 15)return response

openid未绑定用户的处理

  • 为了能够在后续的绑定用户操作中前端可以使用openid,在这里将openid签名后响应给前端。
  • openid属于用户的隐私信息,所以需要将openid签名处理,避免暴露。
try:oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:# 如果openid没绑定商城用户access_token = generate_eccess_token(openid)context = {'access_token': access_token}return render(request, 'oauth_callback.html', context)
else:# 如果openid已绑定商城用户# 实现状态保持qq_user = oauth_user.userlogin(request, qq_user)# 重定向到主页response = redirect(reverse('contents:index'))# 登录时用户名写入到cookie,有效期15天response.set_cookie('username', qq_user.username, max_age=3600 * 24 * 15)return response
  • oauth_callback.html中渲染access_token
<input v-model="access_token" type="hidden" name="access_token" value="{{ access_token }}">

补充itsdangerous的使用,加密openid的传输q

  • itsdangerous模块的参考资料链接 http://itsdangerous.readthedocs.io/en/latest/
  • 安装:pip install itsdangerous
  • TimedJSONWebSignatureSerializer的使用
    – 使用TimedJSONWebSignatureSerializer可以生成带有有效期的token
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings
# serializer = Serializer(秘钥, 有效期秒)
serializer = Serializer(settings.SECRET_KEY, 300)
# serializer.dumps(数据), 返回bytes类型
token = serializer.dumps({'mobile': '185xxxxxxx78'})
token = token.decode()
# 检验token
# 验证失败,会抛出itsdangerous.BadData异常
serializer = Serializer(settings.SECRET_KEY, 300)
try:data = serializer.loads(token)
except BadData:return None

补充:openid签名处理

  • oauth.utils.py
def generate_access_token(openid):"""签名openid:param openid: 用户的openid:return: access_token"""serializer = Serializer(settings.SECRET_KEY, expires_in=constants.ACCESS_TOKEN_EXPIRES)data = {'openid': openid}token = serializer.dumps(data)   # dumps是序列化操作return token.decode()

项目实例代码如下:

第三方QQ登陆视图文件apps/oauth/views.py

"""
第三方QQ登陆视图文件apps/oauth/views.py
"""
from django.shortcuts import render, redirect, reverse
from django.views import View
from QQLoginTool.QQtool import OAuthQQ       # 第三方QQ登陆的类
from django.conf import settings
from django import http
from django.http import HttpResponse
from utils.response_code import RETCODE       # 响应代码
from .models import OAuthQQUser
from .utils import generate_access_token, check_access_token   # 加密解密openid封装的方法
from django.contrib.auth import login, logout    # 登录状态保持和退出# QQ回调用户视图
class QQAuthUserView(View):""" 处理QQ登录回调"""def get(self, request):# 获取code,从url地址上获取code = request.GET.get('code')if not code:return http.HttpResponseForbidden('获取code失败')# 获取QQ登录页面网址oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,redirect_uri=settings.QQ_REDIRECT_URI)# 网络请求异常处理try:# 使用code获取access_tokenaccess_token = oauth.get_access_token(code)# 通过Access Token获取OpenIDopenid = oauth.get_open_id(access_token)except Exception as e:return http.HttpResponseServerError('OAUTH2.0认证失败')# 使用openid 判断该用户是否绑定过商城用户# print(openid)  # 唯一try:oauth_user = OAuthQQUser.objects.get(openid=openid)except Exception as e:# 没有查询到用户的openid,需要返回一个绑定的页面access_token_openid = generate_access_token(openid)       # openid加密# 传递到模板中,当做form表单的参数context = {"access_token_openid": access_token_openid}    # access_token_openid需要加密处理,可逆的加密,md5不可逆return render(request, 'oauth_callback.html', context=context)else:# 查询到了用户的openid,# 状态保持                         # 表示从QQ模型对象中找到对应的用户模型对象login(request, oauth_user.user)   # user与oauth_user是外键关系,保持登陆的应该是user,而不是oauth_usernext = request.GET.get('state')   # url中获取'state'字符串的值if next != "None":       # next是字符串response = redirect(next)     # 跳转至next链接else:# 跳转到首页response = redirect(reverse('contents:index'))# 保存到cookies,保存14天response.set_cookie('username', oauth_user.user.username, max_age=3600*24*14)# 响应结果return response# 第三方QQ登陆视图
class QQAuthURLView(View):"""提供QQ登陆的扫码页面""""""提供QQ登录页面网址https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=xxx&redirect_uri=xxx&state=xxx"""def get(self, request):# next表示从哪个页面进入到的登录页面,将来登录成功后,就自动回到那个页面next = request.GET.get('next')# 获取QQ登录页面网址oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,redirect_uri=settings.QQ_REDIRECT_URI, state=next)# 生成QQ登陆的扫码链接login_url = oauth.get_qq_url()return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'login_url': login_url})

加密openid操作的方法apps/oauth/utils.py

# -*- encoding: utf-8 -*-
"""
@File    : utils.py
@Time    : 2020/8/17 9:45
@Author  : chen加密openid操作的方法:apps/oauth/utils.py
"""
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings         # 导入配置文件
from . import constants                  # 导入定义序列化时效# 加密方法封装,序列化openid
def generate_access_token(openid):"""序列化openid:param openid: 用户的openid明文:return: access_token密文"""# 创建序列化数据serializer = Serializer(settings.SECRET_KEY, expires_in=constants.ACCESS_TOKEN_EXPIRES)# 准备数据data = {'openid': openid}# 数据进行序列化操作token = serializer.dumps(data)# 返回数据序列化之后的数据return token.decode()   # token是字节类型,需要解码# 反序列化access_token,解密操作
def check_access_token(access_token_openid):"""反序列化:param access_token_openid: openid的密文:return:openid的明文"""# 创建序列化对象serializer = Serializer(settings.SECRET_KEY, expires_in=constants.ACCESS_TOKEN_EXPIRES)try:                                                    # 异常处理,防止反序列化失败情况# 解密操作,反序列化操作data = serializer.loads(access_token_openid)        # data 是字典类型数据except Exception as e:return Nonereturn data.get('openid')                           # 查找key返回value值

第三方QQ登录界面templates/oauth_callback.html

{# 第三方QQ登录界面:templates/oauth_callback.html  #}
{% load static %}
<!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>LG商城-绑定用户</title><link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}"><link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"><script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script><script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body><div id="app"><div class="register_con"><div class="l_con fl"><a href="{% url 'contents:index' %}" class="reg_logo"><img src="{% static 'images/2.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 method="post" id="reg_form" @submit="on_submit" v-cloak>{% csrf_token %}<ul><li><label>手机号:</label><input type="text" v-model="mobile" @blur="check_mobile" name="mobile" id="phone"><span v-show="error_mobile" class="error_tip">[[ error_mobile_message ]]</span></li><li><label>密码:</label><input type="password" v-model="password" @blur="check_password" name="password" id="pwd"><span v-show="error_password" class="error_tip">密码最少8位,最长20位</span>{% if account_errmsg %}<span class="error_tip">{{ account_errmsg }}</span>{% endif %}</li><li><label>图形验证码:</label><input type="text" v-model="image_code" @blur="check_image_code" name="image_code" id="pic_code" class="msg_input"><img :src="data:image_code_url" @click="generate_image_code" alt="图形验证码" class="pic_code"><span v-show="error_image_code" class="error_tip">[[ error_image_code_message ]]</span></li><li><label>短信验证码:</label><input type="text" v-model="sms_code" @blur="check_sms_code" name="sms_code" id="msg_code" class="msg_input"><a @click="send_sms_code" class="get_msg_code">[[ sms_code_tip ]]</a><span v-show="error_sms_code" class="error_tip">[[ error_sms_code_message ]]</span>{% if sms_code_errmsg %}<span class="error_tip">{{ sms_code_errmsg }}</span>{% endif %}{% if openid_errmsg %}<span class="error_tip">{{ openid_errmsg }}</span>{% endif %}{% if qq_login_errmsg %}<span class="error_tip">{{ qq_login_errmsg }}</span>{% endif %}</li><input type="hidden" name="access_token_openid" value="{{ access_token_openid }}"><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 © 2016 北京LG商业股份有限公司 All Rights Reserved</p><p>电话:010-****888    京ICP备*******8号</p></div></div><script type="text/javascript" src="{% static 'js/common.js' %}"></script><script type="text/javascript" src="{% static 'js/oauth_callback.js' %}"></script>
</body>
</html>

第三方QQ登录界面静态文件:static/js/oauth_callback.js

// 第三方QQ登录界面静态文件: static/js/oauth_callback.js
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: '',uuid: '',image_code_url: '',sms_code_tip: '获取短信验证码',sending_flag: false,},mounted(){// 生成图形验证码this.generate_image_code();},methods: {// 生成图形验证码generate_image_code(){this.uuid = generateUUID();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+'&uuid='+ this.uuid;axios.get(url, {responseType: 'json'}).then(response => {// 表示后端发送短信成功if (response.data.code == '0') {// 倒计时60秒let num = 60;let t = setInterval(() => {if (num == 1) {clearInterval(t);this.sms_code_tip = '获取短信验证码';this.generate_image_code();this.sending_flag = false;} else {num -= 1;this.sms_code_tip = num + '秒';}}, 1000)} else {if (response.data.code == '4001') {this.error_image_code_message = response.data.errmsg;this.error_image_code = true;} else { // 4002this.error_sms_code_message = response.data.errmsg;this.error_sms_code = true;}this.sending_flag = false;}}).catch(error => {console.log(error.response);this.sending_flag = false;})},// 绑定openidon_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}}}
});

第三方登陆QQ的模型文件:apps/oauth/models.py

"""
第三方登陆QQ的模型文件:apps/oauth/models.py
"""
from django.db import models
from utils.models import BaseModel# Create your models here.class OAuthQQUser(BaseModel):# ForeignKey('users.User') 外键关联User模型,on_delete=models.CASCADE级联删除user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户')openid = models.CharField(max_length=100, verbose_name='openid')     # qq用户登录后的idclass Meta:db_table = 'tb_oauth_qq'verbose_name = 'QQ登陆用户数据'verbose_name_plural = verbose_name

oauth模块中的路由文件: apps/oauth/urls.py

# -*- encoding: utf-8 -*-
"""
@File    : urls.py
@Time    : 2020/8/12 22:05
@Author  : chenoauth模块中的路由文件: apps/oauth/urls.py
"""
from django.urls import path, include
from . import viewsapp_name = 'oauth'urlpatterns = [path('qq/login/', views.QQAuthURLView.as_view()),        # 定义qq登陆路由   提供QQ登陆的扫码页面# 处理QQ登陆后的回调path('oauth_callback/', views.QQAuthUserView.as_view()),
]

定义oauth模块中的常量apps/oauth/constants.py

# -*- encoding: utf-8 -*-
"""
@File    : constants.py
@Time    : 2020/8/17 9:51
@Author  : chen定义oauth模块中的常量:apps/oauth/constants.py
"""
# 定义oauth模块中TimedJSONWebSignatureSerializer方法的加密时效
ACCESS_TOKEN_EXPIRES = 600

openid绑定用户实现

类似于用户注册的业务逻辑

  • 当用户输入的手机号对应的用户已存在
    • 直接将该已存在用户跟openid绑定
  • 当用户输入的手机号对应的用户不存在
    • 新建一个用户,并跟openid绑定

实现绑定用户的逻辑

第三方QQ登陆视图文件:apps/oauth/views.py

"""
第三方QQ登陆视图文件apps/oauth/views.py
"""
from django.shortcuts import render, redirect, reverse
from django.views import View
from QQLoginTool.QQtool import OAuthQQ       # 第三方QQ登陆的类
from django.conf import settings
from django import http
from django.http import HttpResponse
from utils.response_code import RETCODE       # 响应代码
from .models import OAuthQQUser
from .utils import generate_access_token, check_access_token   # 加密解密openid封装的方法
from django.contrib.auth import login, logout    # 登录状态保持和退出
from django_redis import get_redis_connection    # 连接redis
from users.models import User# QQ回调用户视图
class QQAuthUserView(View):""" 处理QQ登录回调"""def get(self, request):# 获取code,从url地址上获取code = request.GET.get('code')if not code:return http.HttpResponseForbidden('获取code失败')# 获取QQ登录页面网址oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,redirect_uri=settings.QQ_REDIRECT_URI)# 网络请求异常处理try:# 使用code获取access_tokenaccess_token = oauth.get_access_token(code)# 通过Access Token获取OpenIDopenid = oauth.get_open_id(access_token)except Exception as e:return http.HttpResponseServerError('OAUTH2.0认证失败')# 使用openid 判断该用户是否绑定过商城用户# print(openid)  # 唯一try:oauth_user = OAuthQQUser.objects.get(openid=openid)except Exception as e:# 没有查询到用户的openid,需要返回一个绑定的页面access_token_openid = generate_access_token(openid)       # openid加密# 传递到模板中,当做form表单的参数context = {"access_token_openid": access_token_openid}    # access_token_openid需要加密处理,可逆的加密,md5不可逆return render(request, 'oauth_callback.html', context=context)else:# 查询到了用户的openid,# 状态保持                         # 表示从QQ模型对象中找到对应的用户模型对象login(request, oauth_user.user)   # user与oauth_user是外键关系,保持登陆的应该是user,而不是oauth_usernext = request.GET.get('state')   # url中获取'state'字符串的值if next != "None":       # next是字符串response = redirect(next)     # 跳转至next链接else:# 跳转到首页response = redirect(reverse('contents:index'))# 保存到cookies,保存14天response.set_cookie('username', oauth_user.user.username, max_age=3600*24*14)# 响应结果return responsedef post(self, request):""" 实现绑定用户的逻辑 """# 接收参数  前端form表单名称要匹配# 校验参数mobile = request.POST.get('mobile')password = request.POST.get('password')sms_code_client = request.POST.get('sms_code')  # 用户提交的access_token_openid = request.POST.get('access_token_openid')# 判断短信验证码校验,redisredis_conn = get_redis_connection('verify_code')   # 连接验证码数据库sms_code_server = redis_conn.get('sms_%s' % mobile)    # 查询方式和存储方式相同if sms_code_server is None:return render(request, 'oauth_callback.html', {"sms_code_errmsg":"无效的短信验证码!"})if sms_code_server.decode() != sms_code_client:     # redis中获取的是字节数据,需要解码return render(request, 'oauth_callback', {"sms_code_errmsg":"短信验证码错误!"})# 判断openid是否有效openid = check_access_token(access_token_openid)        # 解密openidif not openid:return render(request, "oauth_callback.html", {"sms_code_errmsg":"openid已经失效"})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 render(request, 'oauth_callback.html', {"qq_login_errmsg":"账号密码错误"})try:# 将用户绑定openidqq_user = OAuthQQUser.objects.create(user=user, openid=openid)except Exception as e:return render(request, 'oauth_callback.html', {"qq_login_errmsg": "账号密码错误"})# 实现状态保持   qq用户的状态保持login(request, qq_user.user)# 响应登录结果   获取url中的信息next = request.GET.get('state')if next != 'None':response = redirect(next)        # 存在,定向到该urlelse:response = redirect(reverse('contents:index'))        # 不存在,重定向到首页# 存用户信息到cookieresponse.set_cookie('username', qq_user.user.username, max_age=3600*24*14)return response# 第三方QQ登陆视图
class QQAuthURLView(View):"""提供QQ登陆的扫码页面""""""提供QQ登录页面网址https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=xxx&redirect_uri=xxx&state=xxx"""def get(self, request):# next表示从哪个页面进入到的登录页面,将来登录成功后,就自动回到那个页面next = request.GET.get('next')# 获取QQ登录页面网址oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET,redirect_uri=settings.QQ_REDIRECT_URI, state=next)# 生成QQ登陆的扫码链接login_url = oauth.get_qq_url()return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'login_url': login_url})

2、用户基本信息渲染

用户基本信息逻辑分析

以下是要实现的后端逻辑

  • 用户模型补充email_active字段
  • 查询并渲染用户基本信息
  • 添加邮箱
  • 发送邮箱验证邮件
  • 验证邮箱

查询并渲染用户基本信息

用户模型补充email_active字段

  • 由于在渲染用户基本信息时,需要渲染用户邮箱验证的状态,所以需要给用户模型补充email_active字段
  • 补充完字段后,需要进行迁移。

-用户模型补充email_active字段

"""
模型文件apps/users/models.py"""from django.db import models
from django.contrib.auth.models import AbstractUser# objects = models.Manager()
class User(AbstractUser):"""自定义用户模型类"""mobile = models.CharField(max_length=11, unique=True, verbose_name="手机号")    # 父类中没有的字段# 邮箱是否经过验证的字段   新添加字段email_active = models.BooleanField(default=False, verbose_name="邮箱验证的状态")class Meta:db_table = 'tb_users'verbose_name = '用户'verbose_name_plural = verbose_namedef __str__(self):return self.username

映射到数据库:

渲染用户基本信息

将后端模板数据传递到Vue.js

  • 为了方便实现用户添加邮箱时的界面局部刷新
    – 我们将后端提供的用户数据传入到user_center_info.js
// 个人用户中心前端界面静态文件:static/js/user_center_info.js
let vm = new Vue({el: '#app',delimiters: ['[[', ']]'],data: {username: username,    // 使用 username: getCookie('username'),    // getCookie方法在common.js文件中定义mobile: mobile,email: email,email_active: email_active,set_email: false,error_email: false,send_email_btn_disabled: false,send_email_tip: '重新发送验证邮件',histories: [],},mounted() {// 邮箱是否激活:将Python的bool数据转成JS的bool数据this.email_active = (this.email_active=='True') ? true : false;// 是否在设置邮箱this.set_email = (this.email=='') ? true : false;// 请求浏览历史记录// this.browse_histories();},methods: {// 检查email格式check_email(){let re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/;if (re.test(this.email)) {this.error_email = false;} else {this.error_email = true;}},// 取消保存cancel_email(){this.email = '';this.error_email = false;},// 保存emailsave_email(){// 检查email格式this.check_email();if (this.error_email == false) {let url = '/emails/';axios.put(url, {email: this.email}, {headers: {'X-CSRFToken':getCookie('csrftoken')},responseType: 'json'}).then(response => {if (response.data.code == '0') {this.set_email = false;this.send_email_btn_disabled = true;this.send_email_tip = '已发送验证邮件';} else if (response.data.code == '4101') {location.href = '/login/?next=/info/';} else {console.log(response);}}).catch(error => {console.log(error.response);});}},// 请求浏览历史记录browse_histories(){let url = '/browse_histories/';axios.get(url, {responseType: 'json'}).then(response => {this.histories = response.data.skus;for(let i=0; i<this.histories.length; i++){this.histories[i].url = '/detail/' + this.histories[i].id + '/';}}).catch(error => {console.log(error.response);})},}
});

个人用户中心前端界面文件templates/user_center_info.html

{# 个人用户中心前端界面文件:templates/user_center_info.html #}{% load static %}
<!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>LG商城-用户中心</title><link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}"><link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}"><script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script><script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body><div id="app"><div class="header_con"><div class="header" v-cloak><div class="welcome fl">欢迎来到LG商城!</div><div class="fr"><div v-if="username" class="login_btn fl">欢迎您:<em>[[ username ]]</em><span>|</span><a href="{% url 'users:logout' %}">退出</a></div><div v-else class="login_btn fl"><a href="{% url 'users:login' %}">登录</a><span>|</span><a href="{% url 'users:register' %}">注册</a></div><div class="user_link fl"><span>|</span><a href="{% url 'users:info' %}">用户中心</a><span>|</span><a href="cart.html">我的购物车</a><span>|</span><a href="user_center_order.html">我的订单</a></div></div></div></div><div class="search_bar clearfix">{#    contents命名空间    #}<a href="{% url 'contents:index' %}" class="logo fl"><img src="{% static 'images/1.png' %}"></a><div class="search_wrap fl"><form method="get" action="/search/" class="search_con"><input type="text" class="input_text fl" name="q" placeholder="搜索商品"><input type="submit" class="input_btn fr" name="" value="搜索"></form><ul class="search_suggest fl"><li><a href="#">索尼微单</a></li><li><a href="#">优惠15元</a></li><li><a href="#">美妆个护</a></li><li><a href="#">买2免1</a></li></ul></div></div><div class="main_con clearfix"><div class="left_menu_con clearfix"><h3>用户中心</h3><ul><li><a href="{% url 'users:info' %}" class="active">· 个人信息</a></li><li><a href="">· 收货地址</a></li><li><a href="user_center_order.html">· 全部订单</a></li><li><a href="user_center_pass.html">· 修改密码</a></li></ul></div><div class="right_content clearfix" v-cloak><div class="info_con clearfix"><h3 class="common_title2">基本信息</h3><ul class="user_info_list">
{# 使用后端的传输数据信息    apps/users/views.py文件中的context信息    另一种获取信息方式是从cookie中获取,使用 username: getCookie('username'),    // getCookie方法在common.js文件中定义 #}
{#                    <li><span>用户名:</span>{{ username }}</li>#}
{#                    <li><span>联系方式:</span>{{ mobile }}</li>#}{#     上面是Django的语法形式,下面语句是Vue的形式               #}<li><span>用户名:</span>[[ username ]]</li><li><span>联系方式:</span>[[ mobile ]]</li><li><span>Email:</span><div v-if="set_email"><input v-model="email" @blur="check_email" type="email" name="email" class="email"><input @click="save_email" type="button" name="" value="保 存"><input @click="cancel_email" type="reset" name="" value="取 消"><div v-show="error_email" class="error_email_tip">邮箱格式错误</div></div><div v-else><input v-model="email" type="email" name="email" class="email" readonly><div v-if="email_active">已验证</div><div v-else>待验证<input @click="save_email" :disabled="send_email_btn_disabled" type="button" :value="send_email_tip"></div></div></li></ul></div><h3 class="common_title2">最近浏览</h3><div class="has_view_list" v-cloak><ul class="goods_type_list clearfix"><li v-for="sku in histories"><a :href="sku.url"><img :src="sku.default_image_url"></a><h4><a :href="sku.url">[[ sku.name ]]</a></h4><div class="operate"><span class="price">¥[[ sku.price ]]</span><span class="unit">台</span><a href="javascript:;" class="add_goods" title="加入购物车"></a></div></li></ul></div></div></div><div class="footer"><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 © 2016 北京LG商业股份有限公司 All Rights Reserved</p><p>电话:010-****888    京ICP备*******8号</p></div></div>
{#    将后端Django传输的数据转换成Vue能够采用的语法形式 #}<script type="text/javascript">let username = "{{ username }}";let mobile = "{{ mobile }}";let email = "{{ email }}";let email_active = "{{ email_active }}";</script><script type="text/javascript" src="{% static 'js/common.js' %}"></script><script type="text/javascript" src="{% static 'js/user_center_info.js' %}"></script>
</body>
</html>

视图文件 apps/users/views.py文件,用户后端验证视图文件

"""
视图文件apps/users/views.py文件,用户后端验证视图文件
"""
from django.shortcuts import render, redirect, reverse
from django.http import HttpResponse, JsonResponse
from django.views import View
from .forms import RegisterForm, LoginForm
from .models import User
from django.contrib.auth import login, logout, authenticate    # authenticate封装的验证用户名和密码是否正确的方法
from django_redis import get_redis_connection
from django.contrib.auth.mixins import LoginRequiredMixin      # 验证用户是否登录的类# 个人用户中心
class UserInfoView(LoginRequiredMixin, View):"""用户个人中心"""def get(self, request):"""提供用户个人中心"""'''login_url = Nonepermission_denied_message = ''raise_exception = Falseredirect_field_name = REDIRECT_FIELD_NAME'''# 验证用户是否登陆# if request.user.is_authenticated:#     return render(request, 'user_center_info.html')# else:#     return redirect(reverse('users:login'))           # 用户未登录,跳转至登陆界面# print(request.user)# print(request.user.username)# print(request.user.mobile)# 数据由Django后端来提供,前端数据的读取方式采用Vue方式读取[[ username ]]context = {'username': request.user.username,'mobile': request.user.mobile,}# 上面的代码后期需要复用多次,可以引入LoginRequiredMixin类封装的方法和REDIRECT_FIELD_NAME = 'next'参数来重定向return render(request, 'user_center_info.html', context=context)        # 重定向到个人中心# 退出登录
class LogoutView(View):"""退出登陆逻辑实现"""def get(self, request):"""实现用户退出登录的功能"""# 清除状态保持信息logout(request)# 退出登录之后重定向到首页response = redirect(reverse('contents:index'))# 删除cookies中的用户名# result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周response.delete_cookie('username')return response                           # 响应结果# 用户登陆
class LoginView(View):"""用户名登陆"""def get(self, request):"""  提供登陆界面:return: 登陆界面"""return render(request, 'login.html')def post(self, request):"""实现登录逻辑:param request: 请求对象:return: 登录结果"""# 接受参数login_form = LoginForm(request.POST)# 校验参数if login_form.is_valid():# 接收参数username = login_form.cleaned_data.get('username')password = login_form.cleaned_data.get('password')remembered = request.POST.get('remembered')                 # 没经过form验证,使用request接收参数# 认证登录用户# users = User.objects.get(username=username)# users.check_password(password)                            # check_password验证密码封装的方法,返回值bool类型"""  authenticate方法源码def authenticate(self, request, username=None, password=None, **kwargs):if username is None:username = kwargs.get(UserModel.USERNAME_FIELD)try:user = UserModel._default_manager.get_by_natural_key(username)except UserModel.DoesNotExist:# Run the default password hasher once to reduce the timing# difference between an existing and a nonexistent user (#20760).UserModel().set_password(password)else:if user.check_password(password) and self.user_can_authenticate(user):return user"""user = authenticate(username=username, password=password)   # 重构authenticate方法之后,可以验证手机号登录和用户名登录if user is None:# 用户名或者密码输入错误return render(request, 'login.html', {"errmsg": "用户名或者密码输入错误"})# 实现状态保持login(request, user)# 设置状态保持的周期if remembered != 'on':# 没选中记住密码,浏览器关闭就需要销毁session信息request.session.set_expiry(0)                  # set_expiry过期时间else:# 选中记住密码,session信息默认保存两周# request.session.set_expiry(60*60*24*14)request.session.set_expiry(None)# REDIRECT_FIELD_NAME = 'next'      LoginRequiredMixin类中源码的参数 ,用于获取登陆前的路由请求,方便登陆后直接定向到之前的请求界面next = request.GET.get('next')       # 获取url中的‘next’字符串参数if next:result = redirect(next)          # 如果存在next参数,则重定向到这个地址else:# 后端将用户信息存入cookieresult = redirect(reverse('contents:index'))            # redirect方法源码中会返回一个redirect_class# set_cookie('key', 'value', 'erpriy')   erpriy过期时间result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周# 响应登录结果    跳转到首页return resultelse:print(login_form.errors.get_json_data())context = {"form_errors": login_form.errors,}return render(request, 'login.html', context=context)class RegisterView(View):"""用户注册"""def get(self, request):"""提供用户的注册界面"""return render(request, 'register.html')def post(self, request):"""提供用户的注册逻辑"""# 前端用户提交数据form = RegisterForm(request.POST)if form.is_valid():# 接收参数username = form.cleaned_data.get('username')password = form.cleaned_data.get('password')mobile = form.cleaned_data.get('mobile')sms_code_client = request.POST.get('sms_code')         # 验证短信验证码  sms_code是register.html 文件中命名的# 判断用户输入的短信验证码是否正确redis_conn = get_redis_connection('verify_code')       # 链接redis中配置的数据库sms_code_server = redis_conn.get('sms_%s' % mobile)    # 根据存储时候的格式写if sms_code_server is None:return render(request, 'register.html', {'sms_code_errmsg': '短信验证码已经失效'})     # 错误信息渲染到前端界面if sms_code_server.decode() != sms_code_client:       # sms_code_server数据类型需要转换return render(request, 'register.html', {'sms_code_errmsg': '短信验证码填写错误'})try:# user = User(username=username, password=password, mobile=mobile)# 下面的添加数据的方法是封装了加密等功能的函数,更安全users = User.objects.create_user(username=username, password=password, mobile=mobile)except:    # 如果保存数据失败return render(request, 'register.html', {'register_error_message': '注册失败'})# 保持用户登录的状态login(request, users)# 返回响应# return HttpResponse('success')return redirect(reverse('contents:index'))           # 注册成功,跳转到首页else:print(form.errors.get_json_data())# return HttpResponse("fail")# 返回注册错误信息到前端界面context = {'form_error': form.errors,}return render(request, 'register.html', context=context)# 判断用户名是否已经存在
class UsernameExists(View):""" 判断用户名是否已经存在"""def get(self, request, username):     # username用户名count = User.objects.filter(username=username).count()      # 查询数据库中信息return JsonResponse({"code": 0, "errmsg": "OK", "count": count})   # 返回给前端界面

3、添加和验证邮箱

添加邮箱后端逻辑

添加邮箱接口设计和定义

请求方式

请求参数

响应结果:JSON

添加邮箱后端逻辑实现

用户邮箱绑定:apps/users/views.py文件

"""
视图文件apps/users/views.py文件,用户后端验证视图文件
"""
from django.shortcuts import render, redirect, reverse
from django.http import HttpResponse, JsonResponse
from django.views import View
from .forms import RegisterForm, LoginForm
from .models import User
from django.contrib.auth import login, logout, authenticate    # authenticate封装的验证用户名和密码是否正确的方法
from django_redis import get_redis_connection
from django.contrib.auth.mixins import LoginRequiredMixin      # 验证用户是否登录的类
import json
from django.http import QueryDict      # 转换数据类型# 用户邮箱绑定
class EmailView(View):"""添加邮箱"""# 此时是put请求,一般用于更新数据def put(self, request):"""添加邮箱的业务逻辑实现 """'''put 提交的数据是在body属性中,是字节数据类型Bytesget,post提交的数据是QueryDict的数据类型'''print(request.body.decode())print(type(request.body.decode()))    # str数据类型# 从request中获取emailemail = json.loads(request.body.decode()).get('email')print(email)return HttpResponse('success')# 个人用户中心
class UserInfoView(LoginRequiredMixin, View):"""用户个人中心"""def get(self, request):"""提供用户个人中心"""'''login_url = Nonepermission_denied_message = ''raise_exception = Falseredirect_field_name = REDIRECT_FIELD_NAME'''# 验证用户是否登陆# if request.user.is_authenticated:#     return render(request, 'user_center_info.html')# else:#     return redirect(reverse('users:login'))           # 用户未登录,跳转至登陆界面# print(request.user)# print(request.user.username)# print(request.user.mobile)# 数据由Django后端来提供,前端数据的读取方式采用Vue方式读取[[ username ]]context = {'username': request.user.username,'mobile': request.user.mobile,}# 上面的代码后期需要复用多次,可以引入LoginRequiredMixin类封装的方法和REDIRECT_FIELD_NAME = 'next'参数来重定向return render(request, 'user_center_info.html', context=context)        # 重定向到个人中心# 退出登录
class LogoutView(View):"""退出登陆逻辑实现"""def get(self, request):"""实现用户退出登录的功能"""# 清除状态保持信息logout(request)# 退出登录之后重定向到首页response = redirect(reverse('contents:index'))# 删除cookies中的用户名# result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周response.delete_cookie('username')return response                           # 响应结果# 用户登陆
class LoginView(View):"""用户名登陆"""def get(self, request):"""  提供登陆界面:return: 登陆界面"""return render(request, 'login.html')def post(self, request):"""实现登录逻辑:param request: 请求对象:return: 登录结果"""# 接受参数login_form = LoginForm(request.POST)# 校验参数if login_form.is_valid():# 接收参数username = login_form.cleaned_data.get('username')password = login_form.cleaned_data.get('password')remembered = request.POST.get('remembered')                 # 没经过form验证,使用request接收参数# 认证登录用户# users = User.objects.get(username=username)# users.check_password(password)                            # check_password验证密码封装的方法,返回值bool类型"""  authenticate方法源码def authenticate(self, request, username=None, password=None, **kwargs):if username is None:username = kwargs.get(UserModel.USERNAME_FIELD)try:user = UserModel._default_manager.get_by_natural_key(username)except UserModel.DoesNotExist:# Run the default password hasher once to reduce the timing# difference between an existing and a nonexistent user (#20760).UserModel().set_password(password)else:if user.check_password(password) and self.user_can_authenticate(user):return user"""user = authenticate(username=username, password=password)   # 重构authenticate方法之后,可以验证手机号登录和用户名登录if user is None:# 用户名或者密码输入错误return render(request, 'login.html', {"errmsg": "用户名或者密码输入错误"})# 实现状态保持login(request, user)# 设置状态保持的周期if remembered != 'on':# 没选中记住密码,浏览器关闭就需要销毁session信息request.session.set_expiry(0)                  # set_expiry过期时间else:# 选中记住密码,session信息默认保存两周# request.session.set_expiry(60*60*24*14)request.session.set_expiry(None)# REDIRECT_FIELD_NAME = 'next'      LoginRequiredMixin类中源码的参数 ,用于获取登陆前的路由请求,方便登陆后直接定向到之前的请求界面next = request.GET.get('next')       # 获取url中的‘next’字符串参数if next:result = redirect(next)          # 如果存在next参数,则重定向到这个地址else:# 后端将用户信息存入cookieresult = redirect(reverse('contents:index'))            # redirect方法源码中会返回一个redirect_class# set_cookie('key', 'value', 'erpriy')   erpriy过期时间result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周# 响应登录结果    跳转到首页return resultelse:print(login_form.errors.get_json_data())context = {"form_errors": login_form.errors,}return render(request, 'login.html', context=context)# 用户注册
class RegisterView(View):"""用户注册"""def get(self, request):"""提供用户的注册界面"""return render(request, 'register.html')def post(self, request):"""提供用户的注册逻辑"""# 前端用户提交数据form = RegisterForm(request.POST)if form.is_valid():# 接收参数username = form.cleaned_data.get('username')password = form.cleaned_data.get('password')mobile = form.cleaned_data.get('mobile')sms_code_client = request.POST.get('sms_code')         # 验证短信验证码  sms_code是register.html 文件中命名的# 判断用户输入的短信验证码是否正确redis_conn = get_redis_connection('verify_code')       # 链接redis中配置的数据库sms_code_server = redis_conn.get('sms_%s' % mobile)    # 根据存储时候的格式写if sms_code_server is None:return render(request, 'register.html', {'sms_code_errmsg': '短信验证码已经失效'})     # 错误信息渲染到前端界面if sms_code_server.decode() != sms_code_client:       # sms_code_server数据类型需要转换return render(request, 'register.html', {'sms_code_errmsg': '短信验证码填写错误'})try:# user = User(username=username, password=password, mobile=mobile)# 下面的添加数据的方法是封装了加密等功能的函数,更安全users = User.objects.create_user(username=username, password=password, mobile=mobile)except:    # 如果保存数据失败return render(request, 'register.html', {'register_error_message': '注册失败'})# 保持用户登录的状态login(request, users)# 返回响应# return HttpResponse('success')return redirect(reverse('contents:index'))           # 注册成功,跳转到首页else:print(form.errors.get_json_data())# return HttpResponse("fail")# 返回注册错误信息到前端界面context = {'form_error': form.errors,}return render(request, 'register.html', context=context)# 判断用户名是否已经存在
class UsernameExists(View):""" 判断用户名是否已经存在"""def get(self, request, username):     # username用户名count = User.objects.filter(username=username).count()      # 查询数据库中信息return JsonResponse({"code": 0, "errmsg": "OK", "count": count})   # 返回给前端界面

个人用户中心前端界面静态文件static/js/user_center_info.js

// 个人用户中心前端界面静态文件:static/js/user_center_info.js
let vm = new Vue({el: '#app',delimiters: ['[[', ']]'],data: {username: username,mobile: mobile,email: email,email_active: email_active,set_email: false,error_email: false,send_email_btn_disabled: false,send_email_tip: '重新发送验证邮件',histories: [],},mounted() {// 邮箱是否激活:将Python的bool数据转成JS的bool数据this.email_active = (this.email_active=='True') ? true : false;// 是否在设置邮箱this.set_email = (this.email=='') ? true : false;// 请求浏览历史记录// this.browse_histories();},methods: {// 检查email格式     定义方法check_email(){let re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/;  // 邮箱正则匹配if (re.test(this.email)) {this.error_email = false;} else {this.error_email = true;}},// 取消保存cancel_email(){this.email = '';this.error_email = false;},// 保存emailsave_email(){// 检查email格式this.check_email();if (this.error_email == false) {let url = '/users/emails/';          // 路由  注意前面的/users/不能忘axios.put(url, {                     // put 请求email: this.email}, {headers: {'X-CSRFToken':getCookie('csrftoken')    // 需要带上cookie中的csrftoken},responseType: 'json'}).then(response => {                                   // 成功回调的情况if (response.data.code == '0') {                 // 响应成功  代码 0this.set_email = false;this.send_email_btn_disabled = true;this.send_email_tip = '已发送验证邮件';} else if (response.data.code == '4101') {        // 响应失败  代码 4101location.href = '/users/login/?next=/info/';        // 跳转到该路由  登录界面} else {console.log(response);                         // 异常打印错误}}).catch(error => {                                     // 回调失败情况console.log(error.response);                      // 打印错误信息});}},// 请求浏览历史记录browse_histories(){let url = '/browse_histories/';axios.get(url, {responseType: 'json'}).then(response => {this.histories = response.data.skus;for(let i=0; i<this.histories.length; i++){this.histories[i].url = '/detail/' + this.histories[i].id + '/';}}).catch(error => {console.log(error.response);})},}
});

页面测试正常:

Django项目实战——7—(openid是否绑定用户的处理、用户基本信息渲染、添加和验证邮箱)相关推荐

  1. Django项目实战——6—(退出登录、判断用户是否登录、QQ登录、QQ登录工具QQLoginTool、本机绑定域名)

    1.退出登录 注册界面跳转到登陆界面:templates/register.html 前端注册界面 {# 跳转到登陆界面 #}<a href="{% url 'users:login' ...

  2. Django项目实战——用户投票系统(三)

    Django项目实战--用户投票系统(三) 承接上文 官方文档链接附上: 编写你的第一个 Django 应用,第 3 部分 | Django 文档 | Django (djangoproject.co ...

  3. 美多商城之用户中心(添加和验证邮箱)

    二.添加和验证邮箱 2.1 添加邮箱后端逻辑 1. 添加邮箱接口设计和定义 1.请求方式 选项 方案 请求方法 PUT 请求地址 /emails/ # 添加邮箱url(r'^emails/$', vi ...

  4. Django项目实战——8—(判断用户是否登录并返回JSON、Django发送邮件的配置、生成邮箱验证链接、验证邮箱后端逻辑)

    1.判断用户是否登录并返回JSON 重要提示: 只有用户登录时才能让其绑定邮箱. 此时前后端交互的数据类型是JSON,所以需要判断用户是否登录并返回JSON给用户. 方案一: 使用Django用户认证 ...

  5. Django项目实战 ----用户使用QQ登录

    QQ登录流程 点击前端QQ登录的请求按钮,弹出扫码页面,用于手机扫码授权 如果之前登录过,那么就直接登录成功 如果是第一次使用QQ登录,会提示绑定之前的账号或者输入手机号 QQ登录流程准备的配置工作 ...

  6. Django项目实战——5—(用户登录、首页用户名展示、项目阶段总结)

    1.用户登录 用户名登录逻辑分析 用户名登录接口设计 请求方式 请求参数:表单 响应结果:HTML 用户名登录逻辑实现 用户后端验证视图文件apps/users/views.py "&quo ...

  7. python django项目实例_最新Django项目实战-从零开发NB的任务平台python视频学习教程...

    saas导学视频 .mp4 │ 模态框.zip │ ├─day01 │ │ 01 day01 sass项目介绍 .mp4 │ │ 02 day01 sass项目演示 .mp4 │ │ 04 day01 ...

  8. Django项目实战:CMDB资产扫描和DevOPS自动化运维

    文章目录 项目实战:CMDB自动化资产扫描和自动化运维 1.项目介绍 2.项目技术分析 运维自动化难点和痛点 项目技术难点 整体工程设计 3.项目环境搭建 项目环境要求 项目环境的搭建 项目目录的配置 ...

  9. Django项目实战: Django + PyPDF2实现PDF页面提取和PDF文件输出

    在日常工作中我们经常需要从一个大的PDF文档中提取我们所需要的页面,所以今天我们将教你用Django + PyPDF2开发个小Web应用: 用户上传一个PDF文档,输入需要提取的页面号码,点击确定后浏 ...

最新文章

  1. android jack log,Android:JACK编译错误汇总及解决
  2. 【二分图判定】hdu3478 Catch
  3. 信道模型多径传播阴影衰落——无线接入与定位(2)
  4. 小新air15为啥没人买_联想小新air15怎么样?身边的人用的好像比较少?
  5. C++实现字符串数组的计数功能总结
  6. Django中的 返回json对象的方式
  7. 42. HTTP Cookie
  8. java script特效_javascript 常用特效(40种)
  9. 32位qt程序, 利用32位mysql驱动,连接64位mysql8.0
  10. kubernetes架构及核心概念
  11. axios爬坑之provisional headers are shown
  12. 焦虑的时候听一听,分享我喜欢的一段话,心就安静很多
  13. visual studio C++冒号:与双冒号::的使用说明
  14. npm jdf压缩并上传静态文件到服务器
  15. OPPO开放平台移动应用认领
  16. 二项分布、poisson分布、gamma分布一些关系的笔记
  17. 【php基础入门】小白整理PHP常用的字符串函数使用总结分析(推荐)
  18. asp.net 后台生成二维码及生成带logo的二维码
  19. CSDN有哪些值得学习的专栏?
  20. Elasticsearch2.3.4集群安装指南

热门文章

  1. 公司的耍流氓行为,你知道几个?
  2. 软件测试相关简要记录
  3. 【Elastic Search权威指南 读书小记3】ES之数据操作
  4. ajax请求时拒绝访问,ajax跨域请求js拒绝访问的解决方法
  5. 计数数据分析模型:零膨胀负二项(ZINB)回归模型
  6. 微信接入指南进入开发者模式
  7. 寄存器(8086CPU)概述与作用
  8. JavaScript----与函数大战的207个回合(来日再战)
  9. 聊一下面试经常问的SQL注入
  10. 打印机设置为双面打印