1 用户个人中心说明与邮箱激活字段添加
1.1 个人中心介绍
前端访问个人信息页面时,需要向后端请求个人信息。
在本页面中要显示用户的Email邮箱信息,而对于邮箱信息我们要实现对于邮箱的验证功能,并在本页面中显示邮箱是否已验证,如下所示,
这里有一个邮箱,而邮箱在注册的时候,并没有让用户输入,所以会在这里留一个输入的入口,所以第一次访问基本信息,是这样:
用户保存之后,还要对邮箱进行校验:
1.2 增加激活字段
这里需要修改User模型类,增加邮箱是否验证的字段。
class User(AbstractUser):
"""
用户信息
"""
mobile = models.CharField(max_length=11, unique=True, verbose_name="手机号")
email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态')
进行数据库迁移
python manage.py makemigrations
python manage.py migrate
2 返回用户跟人信息数据后端接口
2.1 后端逻辑
2.1.1 后端接口设计:
请求方式: GET /user/
请求参数: 无
返回数据: JSON
返回值 | 类型 | 是否必须 | 说明 |
---|---|---|---|
id | int | 是 | 用户id |
username | str | 是 | 用户名 |
mobile | str | 是 | 手机号 |
str | 是 | email邮箱 | |
email_active | bool | 是 | 邮箱是否通过验证 |
根据接口增加视图逻辑:
而get中的逻辑,其实就是获取详情的逻辑,所以我们可以继承RetrieveModelMixin
还可以直接继承RetrieveAPIView:
继承如下:
序列化器如下:
序列化器代码如下:
class UserDetailSerializer(serializers.ModelSerializer):
"""
用户详细信息序列化器
"""
class Meta:
model = User
fields = ('id', 'username', 'mobile', 'email', 'email_active')
下来指定查询集:
但是RetrieveAPI中获取详情数据的url是/users/<pk>/而我们设计的接口是/user/。
那只能重写get_object了:
这里是如何获取的user对象呢?
2.1.2 如何获取user
而这里我们压根不用查询数据库,因为我们直接返回登录成功的用户信息即可,关键就是如何获取登录成功的用户。
如何获取用户呢?之前在讲django的时候,说过HttpRequest对象中就有这个已经登录的用户对象user:
但是现在是在类视图的函数中,如何拿到request对象呢?
所以代码如下:
from rest_framework.permissions import IsAuthenticated
class UserDetailView(RetrieveAPIView):
"""
用户详情
"""
serializer_class = serializers.UserDetailSerializer
permission_classes = [IsAuthenticated]
def get_object(self):
return self.request.user
注意:访问视图必须要求用户已通过认证(即登录之后)
2.1.3 认证授权
这个视图我们应该是要求用户必须登录之后,才能访问,那怎么办?
来用DRF提供的认证授权机制:
APIView支持的属性如下:
这里就有一个permission_classes,可以知道检测类型。
执行的权限检测类型为:
我们这里选择仅通过认证的用户(也就是仅登录的用户):
权限使用方式如下:
我们选择第二种即可,所以代码如下
2.2 前端请求获取用户个人信息
修改user_center_info.html,增加Vue的变量
<!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="css/reset.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
<script type="text/javascript" src="js/host.js"></script>
<script type="text/javascript" src="js/vue-2.5.16.js"></script>
<script type="text/javascript" src="js/axios-0.18.0.min.js"></script>
<script>
var user_id = sessionStorage.user_id || localStorage.user_id;
var token = sessionStorage.token || localStorage.token;
if (!(user_id && token)) {
location.href = '/login.html?next=/user_center_info.html';
}
</script>
</head>
<body>
<div id="app" v-cloak>
<div class="header_con">
<div class="header">
<div class="welcome fl">欢迎来到美多商城!</div>
<div class="fr">
<div class="login_btn fl">
欢迎您:<em>{{ username }}</em>
<span>|</span>
<a @click="logout">退出</a>
</div>
<div class="user_link fl">
<span>|</span>
<a href="user_center_info.html">用户中心</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">
<a href="index.html" class="logo fl"><img src="images/logo.png"></a>
<div class="sub_page_name fl">| 用户中心</div>
<form method="get" action="/search.html" class="search_con fr mt40">
<input type="text" class="input_text fl" name="q" placeholder="搜索商品">
<input type="submit" class="input_btn fr" name="" value="搜索">
</form>
</div>
<div class="main_con clearfix">
<div class="left_menu_con clearfix">
<h3>用户中心</h3>
<ul>
<li><a href="user_center_info.html" class="active">· 个人信息</a></li>
<li><a href="user_center_order.html">· 全部订单</a></li>
<li><a href="user_center_site.html">· 收货地址</a></li>
<li><a href="user_center_pass.html">· 修改密码</a></li>
</ul>
</div>
<div class="right_content clearfix">
<div class="info_con clearfix">
<h3 class="common_title2">基本信息</h3>
<ul class="user_info_list">
<li><span>用户名:</span>{{ username }}</li>
<li><span>手机号:</span>{{ mobile }}</li>
<li>
<span>Email:</span>
<div v-if="set_email">
<input v-model="email" type="email" name="email">
<input @click="save_email" type="button" name="" value="保 存">
<input @click="set_email=false" type="reset" name="" value="取 消">
<div v-if="email_error">邮箱格式错误</div>
</div>
<div v-else-if="email">
{{ email }}
<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>
<div v-else>
<input @click="set_email=true" type="button" name="" value="设 置">
</div>
</li>
</ul>
</div>
<h3 class="common_title2">最近浏览</h3>
<div class="has_view_list">
<ul class="goods_type_list clearfix">
<li>
<a href="detail.html"><img src="images/goods/goods003.jpg"></a>
<h4><a href="detail.html">360手机 N6 Pro 全网通</a></h4>
<div class="operate">
<span class="prize">¥2699.00</span>
<span class="unit">台</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods004.jpg"></a>
<h4><a href="#">360手机 N6 Pro 全网通</a></h4>
<div class="operate">
<span class="prize">¥2699.00</span>
<span class="unit">台</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods005.jpg"></a>
<h4><a href="#">360手机 N6 Pro 全网通</a></h4>
<div class="operate">
<span class="prize">¥2699.00</span>
<span class="unit">台</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods006.jpg"></a>
<h4><a href="#">360手机 N6 Pro 全网通</a></h4>
<div class="operate">
<span class="prize">¥2699.00</span>
<span class="unit">台</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods007.jpg"></a>
<h4><a href="#">急速路由</a></h4>
<div class="operate">
<span class="prize">¥64.5</span>
<span class="unit">6.45/500g</span>
<a href="#" 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 北京美多商业股份有限公司 All Rights Reserved</p>
<p>电话:010-****888 京ICP备*******8号</p>
</div>
</div>
<script type="text/javascript" src="js/user_center_info.js"></script>
</body>
</html>
在js目录中新建user_center_info.js
var vm = new Vue({
el: '#app',
data: {
host,
user_id: sessionStorage.user_id || localStorage.user_id,
token: sessionStorage.token || localStorage.token,
username: '',
mobile: '',
email: '',
email_active: false,
set_email: false,
send_email_btn_disabled: false,
send_email_tip: '重新发送验证邮件',
email_error: false,
histories: []
},
mounted: function(){
// 判断用户的登录状态
if (this.user_id && this.token) {
axios.get(this.host + '/user/', {
// 向后端传递JWT token的方法
headers: {
'Authorization': 'JWT ' + this.token
},
responseType: 'json',
})
.then(response => {
// 加载用户数据
this.user_id = response.data.id;
this.username = response.data.username;
this.mobile = response.data.mobile;
this.email = response.data.email;
this.email_active = response.data.email_active;
})
.catch(error => {
if (error.response.status==401 || error.response.status==403) {
location.href = '/login.html?next=/user_center_info.html';
}
});
} else {
location.href = '/login.html?next=/user_center_info.html';
}
},
methods: {
// 退出
logout: function(){
sessionStorage.clear();
localStorage.clear();
location.href = '/login.html';
},
// 保存email
save_email: function(){
}
}
});
3 邮件与验证
业务说明:
在用户中心页面中,我们允许用户设置邮箱
当用户点击保存后,我们会向用户发送邮件以验证邮箱的有效性。
为了避免用户未收到验证邮箱,我们提供“重新发送验证邮件”按钮允许用户重新发送邮件。
邮箱验证成功,显示已验证。
技术说明:
在邮件中提供的激活链接地址,为了能区分是哪个用户在进行邮箱验证,需要在链接中包含用户和邮箱的识别信息,如user_id和email数据,但是基于安全性的考虑,不能将这两个数据直接暴露在邮件链接中,而是需要进行隐藏和签名处理(能够检测出是否修改过链接数据)。可以使用前面学过的itsdangerous对user_id和email数据进行处理,生成token作为链接的参数。
4 使用Django发送邮件
我们需要在保存邮箱的同时,发送一封邮件,那我们需要先分析一下如何发送邮件。
那我们如何给发送邮件服务器发送一个smtp协议的请求,去发送邮件呢?
我们需要找一个发送邮件的服务器,我们以163为例。
Django中内置了邮件发送功能,被定义在django.core.mail模块中。发送邮件需要使用SMTP服务器,常用的免费服务器有:163、126、QQ,下面以163邮件为例。
1)注册163邮箱itcast88,登录后设置。
2)在新页面中点击“客户端授权密码”,勾选“开启”,弹出新窗口填写手机验证码。
3)填写授权码。
4)提示开启成功。
5) 在Django配置文件中,设置邮箱的配置信息
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
#发送邮件的邮箱
EMAIL_HOST_USER = 'itcast88@163.com'
#在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'python808'
#收件人看到的发件人
EMAIL_FROM = 'python<itcast88@163.com>'
6) 使用Django提供的模块发送邮件
在django.core.mail
模块提供了send_mail
来发送邮件。
send_mail
(subject, message, from_email, recipient_list,html_message=None)
- subject 邮件标题
- message 普通邮件正文, 普通字符串
- from_email 发件人
- recipient_list 收件人列表
- html_message 多媒体邮件正文,可以是html字符串
例如:
msg='<a href="http://www.itcast.cn/subject/pythonzly/index.shtml" target="_blank">点击激活</a>'
send_mail('注册激活','',settings.EMAIL_FROM, ['itcast88@163.com'], html_message=msg)
5 保存邮箱并发送验证邮件
5.1 保存邮箱后端接口实现
接下来处理保存邮箱,界面如下:
后端接口设计:
请求方式:PUT /email/
请求参数: JSON 或 表单
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
str | 是 | Email邮箱 |
返回数据: JSON
返回值 | 类型 | 是否必须 | 说明 |
---|---|---|---|
id | int | 是 | 用户id |
str | 是 | Email邮箱 |
具体逻辑分析如下:
上述的逻辑其实就是更新的一个逻辑,所以我们可以继承UpdateModelMixin:
源码如下,大家可以看下update方法,做的事情与我们分析的是一致的:
当然我们会直接继承UpdateApiView:
继承之后代码如下:
注意:这里为啥要重写get_object,因为我们的url是不接受pk参数的,所以UpdateApiView无法确定我们要更新哪个模型类,所以我们要重写get_object,告诉他更新哪个模型类。我们这里更新的是user模型类。
序列化器如下:
序列化器中就两个字段id和email,关于这俩字段的方向问题,都不用处理。
id,默认只能做序列化操作。
email,序列化器和反序列化都要做,也是默认。
注意:这里咱们还没有处理到更新邮箱的逻辑,我们在下边发送邮件的时候,处理。
5.2 定义发送邮件的任务
配置Email:
celery_tasks新建任务email:
配置任务: