本教程的知识点为: 项目准备 项目准备 配置 1. 修改settings/dev.py 文件中的路径信息 2. INSTALLED_APPS 3. 数据库 用户部分 图片 1. 后端接口设计: 视图原型 2. 具体视图实现 用户部分 使用Celery完成发送 判断帐号是否存在 1. 判断用户名是否存在 后端接口设计: 用户部分 JWT 什么是JWT 起源 传统的session认证 用户部分 登录 1. 业务说明 2. 后端接口设计 3. 后端实现 登录 使用登录的流程 创建模型类 urllib使用说明 登录回调处理 登录 使用登录的流程 创建模型类 urllib使用说明 绑定用户身份接口 邮件与验证 学习目标: 业务说明: 技术说明: 保存邮箱并发送验证邮件 省市区地址查询 数据库建表 说明 页面静态化 注意 定时任务 安装 部分 详情页 异步任务的触发 。 后端接口设计 收货地址 使用缓存 安装 使用方法 为省市区视图添加缓存 数据库表设计 表结构 数据表结构 首页数据表结构 Docker使用 Docker简介 用户浏览历史记录 1. 保存 后端接口设计 后端实现 搜索 1. 需求分析 2. 搜索引擎原理 3. Elasticsearch 部分 业务需求分析 技术实现 数据存储设计 1. Redis保存已登录用户 商品部分 业务需求分析 技术实现 查询数据 1. 后端接口设计 部分 业务需求分析 技术实现 登录合并 修改登录视图 部分 保存 1. 后端接口设计 2. 后端实现 保存的思路 创建数据库模型类 接入 开发平台登录 沙箱环境 Xadmin 1. 安装 2. 使用 站点的全局配置 站点Model管理。 在Ubuntu中安装 2. 启动与停止 3. 镜像操作 端与自定义文件存储系统 1. 的Python客户端 安装 使用。
仓库里完整资料代码:
博客文章1: https://www.jianshu.com/p/9505b48f5490
博客文章2: https://www.jianshu.com/p/0573183fc4ed
感兴趣的小伙伴可以自取哦~
全套教程部分目录:
部分文件图片:
用户部分
使用Celery完成发送短信
在meiduo/meiduo_mall
下创建celery_tasks用于保存celery异步任务。
在celery_tasks目录下创建config.py文件,用于保存celery的配置信息
broker_url = "redis://127.0.0.1/14"
在celery_tasks目录下创建main.py文件,用于作为celery的启动文件
from celery import Celery
# 为celery使用django配置文件进行设置
import os
if not os.getenv('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings.dev'
# 创建celery应用
app = Celery('meiduo')
# 导入celery配置
app.config_from_object('celery_tasks.config')
# 自动注册celery任务
app.autodiscover_tasks(['celery_tasks.sms'])
在celery_tasks目录下创建sms目录,用于放置发送短信的异步任务相关代码。
将提供的发送短信的云通讯SDK放到celery_tasks/sms/目录下。
在celery_tasks/sms/目录下创建tasks.py文件,用于保存发送短信的异步任务
import logging
from celery_tasks.main import app
from .yuntongxun.sms import CCP
logger = logging.getLogger("django")
# 验证码短信模板
SMS_CODE_TEMP_ID = 1
@app.task(name='send_sms_code')
def send_sms_code(mobile, code, expires):
"""
发送短信验证码
:param mobile: 手机号
:param code: 验证码
:param expires: 有效期
:return: None
"""
try:
ccp = CCP()
result = ccp.send_template_sms(mobile, [code, expires], SMS_CODE_TEMP_ID)
except Exception as e:
logger.error("发送验证码短信[异常][ mobile: %s, message: %s ]" % (mobile, e))
else:
if result == 0:
logger.info("发送验证码短信[正常][ mobile: %s ]" % mobile)
else:
logger.warning("发送验证码短信[失败][ mobile: %s ]" % mobile)
在verifications/views.py中改写SMSCodeView视图,使用celery异步任务发送短信
from celery_tasks.sms import tasks as sms_tasks
class SMSCodeView(GenericAPIView):
...
# 发送短信验证码
sms_code_expires = str(constants.SMS_CODE_REDIS_EXPIRES // 60)
sms_tasks.send_sms_code.delay(mobile, sms_code, sms_code_expires)
return Response({"message": "OK"})
判断帐号是否存在
1. 判断用户名是否存在
后端接口设计:
请求方式: GET usernames/(?P<username>\w{5,20})/count/
请求参数: 路径参数
参数 | 类型 | 是否必传 | 说明 |
---|---|---|---|
username | str | 是 | 用户名 |
返回数据: JSON
{
"username": "itcast",
"count": "1"
}
返回值 | 类型 | 是否必须 | 说明 |
---|---|---|---|
username | str | 是 | 用户名 |
count | int | 是 | 数量 |
后端实现
在users/views.py中定义视图
# url(r'^usernames/(?P<username>\w{5,20})/count/$', views.UsernameCountView.as_view()),
class UsernameCountView(APIView):
"""
用户名数量
"""
def get(self, request, username):
"""
获取指定用户名数量
"""
count = User.objects.filter(username=username).count()
data = {
'username': username,
'count': count
}
return Response(data)
前端实现
在js/register.js中修改
// 检查用户名
check_username: function (){
var len = this.username.length;
if(len<5||len>20) {
this.error_name_message = '请输入5-20个字符的用户名';
this.error_name = true;
} else {
this.error_name = false;
}
// 检查重名
if (this.error_name == false) {
axios.get(this.host + '/usernames/' + this.username + '/count/', {
responseType: 'json'
})
.then(response => {
if (response.data.count > 0) {
this.error_name_message = '用户名已存在';
this.error_name = true;
} else {
this.error_name = false;
}
})
.catch(error => {
console.log(error.response.data);
})
}
},
2. 判断手机号是否存在:
后端接口设计:
请求方式: GET mobiles/(?P<mobile>1[3-9]\d{9})/count
请求参数: 路径参数
参数 | 类型 | 是否必须 | 说明 |
---|---|---|---|
mobile | str | 是 | 手机号 |
返回数据: JSON
{
"mobile": "18512345678",
"count": 0
}
返回值 | 类型 | 是否必须 | 说明 |
---|---|---|---|
mobile | str | 是 | 手机号 |
count | int | 是 | 数量 |
后端实现
在users/views.py中定义视图
# url(r'^mobiles/(?P<mobile>1[3-9]\d{9})/count/$', views.MobileCountView.as_view()),
class MobileCountView(APIView):
"""
手机号数量
"""
def get(self, request, mobile):
"""
获取指定手机号数量
"""
count = User.objects.filter(mobile=mobile).count()
data = {
'mobile': mobile,
'count': count
}
return Response(data)
前端实现
在js/register.js中修改
// 检查手机号
check_phone: function (){
var re = /^1[345789]\d{9}$/;
if(re.test(this.mobile)) {
this.error_phone = false;
} else {
this.error_phone_message = '您输入的手机号格式不正确';
this.error_phone = true;
}
if (this.error_phone == false) {
axios.get(this.host + '/mobiles/'+ this.mobile + '/count/', {
responseType: 'json'
})
.then(response => {
if (response.data.count > 0) {
this.error_phone_message = '手机号已存在';
this.error_phone = true;
} else {
this.error_phone = false;
}
})
.catch(error => {
console.log(error.response.data);
})
}
},
注册
1. 后端接口设计:
请求方式: POST /users/
请求参数: JSON 或 表单
参数名 | 类型 | 是否必须 | 说明 |
---|---|---|---|
username | str | 是 | 用户名 |
password | str | 是 | 密码 |
password2 | str | 是 | 确认密码 |
sms_code | str | 是 | 短信验证码 |
mobile | str | 是 | 手机号 |
allow | str | 是 | 是否同意用户协议 |
返回数据: JSON
{
"id": 9,
"username": "python8",
"mobile": "18512345678",
}
返回值 | 类型 | 是否必须 | 说明 |
---|---|---|---|
id | int | 是 | 用户id |
username | str | 是 | 用户名 |
mobile | str | 是 | 手机号 |
视图原型
# url(r'^users/$', views.UserView.as_view()),
class UserView(CreateAPIView):
"""
用户注册
传入参数:
username, password, password2, sms_code, mobile, allow
"""
pass
2. 后端实现
在users/serializers.py中创建序列化器对象
class CreateUserSerializer(serializers.ModelSerializer):
"""
创建用户序列化器
"""
password2 = serializers.CharField(label='确认密码', write_only=True)
sms_code = serializers.CharField(label='短信验证码', write_only=True)
allow = serializers.CharField(label='同意协议', write_only=True)
class Meta:
model = User
fields = ('id', 'username', 'password', 'password2', 'sms_code', 'mobile', 'allow')
extra_kwargs = {
'username': {
'min_length': 5,
'max_length': 20,
'error_messages': {
'min_length': '仅允许5-20个字符的用户名',
'max_length': '仅允许5-20个字符的用户名',
}
},
'password': {
'write_only': True,
'min_length': 8,
'max_length': 20,
'error_messages': {
'min_length': '仅允许8-20个字符的密码',
'max_length': '仅允许8-20个字符的密码',
}
}
}
def validate_mobile(self, value):
"""验证手机号"""
if not re.match(r'^1[3-9]\d{9}$', value):
raise serializers.ValidationError('手机号格式错误')
return value
def validate_allow(self, value):
"""检验用户是否同意协议"""
if value != 'true':
raise serializers.ValidationError('请同意用户协议')
return value
def validate(self, data):
# 判断两次密码
if data['password'] != data['password2']:
raise serializers.ValidationError('两次密码不一致')
# 判断短信验证码
redis_conn = get_redis_connection('verify_codes')
mobile = data['mobile']
real_sms_code = redis_conn.get('sms_%s' % mobile)
if real_sms_code is None:
raise serializers.ValidationError('无效的短信验证码')
if data['sms_code'] != real_sms_code.decode():
raise serializers.ValidationError('短信验证码错误')
return data
def create(self, validated_data):
"""
创建用户
"""
# 移除数据库模型类中不存在的属性
del validated_data['password2']
del validated_data['sms_code']
del validated_data['allow']
user = super().create(validated_data)
# 调用django的认证系统加密密码
user.set_password(validated_data['password'])
user.save()
return user
在users/views.py中定义视图
class UserView(CreateAPIView):
"""
用户注册
"""
serializer_class = serializers.CreateUserSerializer
3. 前端编写
修改js/register.js
// 注册
on_submit: function(){
this.check_username();
this.check_pwd();
this.check_cpwd();
this.check_phone();
this.check_sms_code();
this.check_allow();
if(this.error_name == false && this.error_password == false && this.error_check_password == false
&& this.error_phone == false && this.error_sms_code == false && this.error_allow == false) {
axios.post(this.host + '/users/', {
username: this.username,
password: this.password,
password2: this.password2,
mobile: this.mobile,
sms_code: this.sms_code,
allow: this.allow.toString()
}, {
responseType: 'json'
})
.then(response => {
location.href = '/index.html';
})
.catch(error=> {
if (error.response.status == 400) {
if ('non_field_errors' in error.response.data) {
this.error_sms_code_message = error.response.data.non_field_errors[0];
} else {
this.error_sms_code_message = '数据有误';
}
this.error_sms_code = true;
} else {
console.log(error.response.data);
}
})
}
}