user项目
user项目准备
> python manage.py startapp user #上一篇已完成基础配置
#dashopt/settings.py
'user',
#user/models.py
from django.db import models
class UserProfile(models.Model):
"""用户表"""
# 用户名 密码 邮箱 手机号 是否激活 创建时间 更新时间
username = models.CharField(max_length=11, verbose_name="用户名", unique=True)
password = models.CharField(max_length=32)
email = models.EmailField()
phone = models.CharField(max_length=11)
# 是否激活
is_active = models.BooleanField(default=False, verbose_name="是否激活")
# 创建时间 和 更新时间
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
# 修改表名
class Meta:
# 表名: 应用名_类名非驼峰
db_table = "user_user_profile"
> python manage.py makemigrations
> python manage.py migrate
1、注册功能API
接口文档
用户注册功能API,URL:http://127.0.0.1:8000/v1/users
请求方法:POST
请求参数:JSON
字段 | 含义 | 类型 | 备注 |
---|---|---|---|
uname | 用户名 | char | 必填 |
password | 密码 | char | 必填 |
phone | 手机号 | char | 必填 |
邮箱 | char | 必填 | |
carts | 购物车中商品种类数量 | int | 必填 |
请求示例
{
'uname':'zhaoliying',
'password':'123456',
'phone':'13603263333',
'email':'zhaoliying@tedu.cn',
'carts': '0'
}
响应格式
响应数据说明
字段 | 含义 | 类型 | 备注 |
---|---|---|---|
code | 状态码 | int | 必填 |
username | 用户名 | char | 与error二选一 |
data | 返回数据[token] | dict | 与error二选一 |
carts_count | 购物车商品种类数量 | int | 与error二选一 |
error | 错误原因 | char | 错误时填写 |
正确示例
{
'code': 200,
'username': 'zhaoliying',
'data': { 'token': token },
'carts_count': 0
}
错误示例
{
'code': xxx,
'error': 'error reason'
}
设计路由
请求1:http://127.0.0.1:8000/v1/users
请求2:http://127.0.0.1:8000/v1/users/test
# 方案1
主路由: path('v1/users', include('user.urls')),
分布式路由: path('', views.xxx),
# 方案2:扩展性更高
主路由: path('v1/users', user_views.xxx),
初步设计验证
#dashopt/urls.py
from user import views as user_views
path('v1/users', user_views.users),
#user/views.py
from django.http import JsonResponse
def users(request):
return JsonResponse({'code':200})
#测试验证,ok后继续写功能代码
http://localhost:8000/v1/users 返回 {"code": 200}
完善功能
#user/views.py
import time
import jwt
import json
from hashlib import md5
from django.http import JsonResponse
from .models import UserProfile
from django.conf import settings
def users(request):
"""
注册模块视图逻辑
"""
# 1.获取请求体数据 根据接口文档前端的请求示例信息去获取
data = json.loads(request.body)
uname = data.get('uname')
password = data.get('password')
phone = data.get('phone')
email = data.get('email')
# 2.数据校验[此处暂不做校验]
# 3.判断用户名是否已被占用
# 3.1 被占用: 返回
# 3.2 未被占用: 存入数据表
# 4.签发token
# 5.组织数据返回,注册成功
old_users = UserProfile.objects.filter(username=uname)
# 已被占用
if old_users:
result = {
'code': 10100,
'error': 'The username is existed'
}
return JsonResponse(result)
# 未被占用,存入数据表,先处理下密码明文问题
m = md5()
m.update(password.encode())
password = m.hexdigest()
# 考虑数据库并发
try:
user = UserProfile.objects.create(username=uname, password=password, email=email, phone=phone)
except Exception as e:
print('create user error %s' % e)
#result 接口文档
result = {
'code': 10101,
'error': 'The username is existed'
}
return JsonResponse(result)
# 签发token[默认1天]
token = make_token(uname)
#result 接口文档
result = {
'code': 200,
'username': uname,
'data': {'token': token},
'carts_count': 0
}
return JsonResponse(result)
def make_token(uname, expire=3600*24):
"""
功能函数: 生成token
:param uname: 用户名
:return: token
"""
payload = {
'exp': time.time() + expire,
'username': uname
}
key = settings.JWT_TOKEN_KEY #settings.py里定义
return jwt.encode(payload, key, algorithm="HS256")
#dashopt/settings.py
...
JWT_TOKEN_KEY = '123456'
http://127.0.0.1:7000/dadashop/templates/register.html
#注册弹出 注册成功!确定返回主页 ,重复注册弹出The username is existed
#浏览器Local Storage 存储
dashop_count 0
dashop_token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOj....
dashop_user user02
#数据接收,效果展现,参考下面的前端代码
...
<div class="form-group">
<label for="uname">用户名:</label>
<input autocomplete required minlength="6" maxlength="11" type="text" placeholder="请输入用户名" autofocus name="uname"
id="uname" />
<span class="msg-default" >用户名长度在6到11位之间</span>
</div>
<div class="form-group">
<label for="upwd">登录密码:</label>
<input required type="password" minlength="6" maxlength="12" placeholder="请输入密码" name="upwd" autofocus id="upwd" />
<span class="msg-default">密码长度在6到12位之间</span>
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input autocomplete required type="email" placeholder="请输入邮箱地址" value="dada@tedu.cn" name="email" id="email" />
<span class="msg-default hidden">请输入合法的邮箱地址</span>
</div>
<div class="form-group">
<label for="phone">手机号:</label>
<input id="phone" name="phone" placeholder="请输入您的手机号" required type="text" value="" />
<span class="msg-default hidden">请输入合法的手机号</span>
</div>
...
success: function (data) { //成功的回调函数
if (data.code === 200){
//存储数据到本地
window.localStorage.setItem('dashop_token', data.data.token);
window.localStorage.setItem('dashop_user', data.username);
window.localStorage.setItem('dashop_count', data.carts_count);
alert('注册成功!'); //弹窗
window.location.href = '/dadashop/templates/index.html';//回主页
}else {
alert(data.error); //后端返回的error值信息
}
},
2、登录功能API
接口文档
用户登录功能API,URL:http://127.0.0.1:8000/v1/tokens
请求方法:POST,请求参数:JSON
字段 | 含义 | 类型 | 备注 |
---|---|---|---|
username | 用户名 | char | 必填 |
password | 密码 | char | 必填 |
carts | 购物车商品种类数量 | int | 必填 |
请求示例
{
'username': 'zhaoliying',
'password': '123456',
'carts': 0
}
响应格式
正确示例
{
'code': 200,
'username': 'zhaoliying',
'data': { 'token': token },
'carts_count': 0
}
错误示例
{
'code': xxx,
'error': 'error reason'
}
响应数据说明
字段 | 含义 | 类型 | 备注 |
---|---|---|---|
code | 状态码 | int | 必填 |
username | 用户名 | char | 与error二选一 |
data | 返回数据[token] | dict | 与error二选一 |
carts_count | 购物车商品种类数量 | int | 与error二选一 |
error | 错误原因 | char | 错误时填写 |
路由设计
独立app,方便管理(重要且使用较多)
> python manage.py startapp dtoken
#dashopt/settings.py
...
'dtoken',
#dashopt/urls.py
from dtoken import views as token_views
...
# 用户模块:登录 v1/tokens
path('v1/tokens', token_views.tokens),
#dtoken/views.py
from django.http import HttpResponse
def tokens(request):
return HttpResponse('test is ok')
import jwt
import json
import time
from hashlib import md5
from django.conf import settings
from django.http import JsonResponse
from user.models import UserProfile
def tokens(request):
"""
登录模块视图逻辑
1.获取请求体数据
2.判断用户名是否存在[UserProfile查询]
3.判断密码
4.签发token,组织数据返回
"""
data = json.loads(request.body)
username = data.get('username')
password = data.get('password')
# 用户名是否存在
try:
user = UserProfile.objects.get(username=username)
except Exception as e:
# 用户名错误
print('get user error is %s' % e)
result = {'code': 10200, 'error': 'The username is wrong'}
return JsonResponse(result)
# 用户名存在,判断密码
m = md5()
m.update(password.encode())
if m.hexdigest() != user.password:
# 密码错误
result = {'code': 10201, 'error': 'The password is wrong'}
return JsonResponse(result)
# 签发token[默认1天]
token = make_token(username)
result = {
'code': 200,
'username': username,
'data': {'token': token},
'carts_count': 0
}
return JsonResponse(result)
def make_token(username, expire=86400):
"""功能函数:生成token"""
payload = {
"username": username,
"exp": time.time() + expire
}
key = settings.JWT_TOKEN_KEY
return jwt.encode(payload, key, algorithm="HS256")
http://localhost:7000/dadashop/templates/index.html
//登录成功,查看浏览器存储,login.html代码,账号、密码错误时返回的 The username is wrong
<input type="text" placeholder="请输入您的用户名" name="username" id="username" required>
<input type="password" id="password" placeholder="请输入您的密码" name="password" required minlength="6"
if (username != '' && password != '') {
$.ajax({
type: 'POST',
url: baseUrl+'/v1/tokens',
contentType:'application/json',
data: JSON.stringify(inputData),
success: function (result) {
if (result.code == 200) { //登录成功
window.localStorage.clear()
window.localStorage.setItem('dashop_token', result.data.token);
window.localStorage.setItem('dashop_count',result.carts_count);
window.localStorage.setItem('dashop_user', result.username);
alert('登录成功');
window.location.href = 'index.html';
} else { //登录失败
$('#showResult').html(result.error);
}
}
});
3、邮箱激活
功能流程思考
- 用户提交注册信息,会到后端注册视图(users),发送激活邮件给用户邮箱
- 激活邮件中包含激活链接
- 用户打开邮件,用户点击激活链接,跳转到激活页面
- 激活页面:是 和 否,用户点击是,把数据库表中该用户的is_active=True
具体细节思考
- 发邮件【后端】 from django.core.mail import send_mail
- 激活链接【前端提供,后端放到邮件中】http://127.0.0.1:7000/dadashop/templates/active.html
- 激活页面【前端】
- 用户点击 是,发送xhr请求到后端,该数据库【后端】
问题
场景:
用户进入收件箱,点开了激活邮件;
用户点击 激活链接 http://xxx/active.html?uname=xxx 跳转激活页面
用户点击 是 --> xhr请求到后端
后端给该用户进行激活【is_activer=True】 给谁激活???
所以:
在激活链接中通过查询字符串?带有用户标识
查询字符串,从后端-->用户邮箱-->激活页面-->是-->后端路由-->后端视图-->request.GET.get('uname') --> is_active=True
小问题:查询字符串怎么标识?
方案1:?code=zhaoliying # 明文,不太安全
方案2:?code=base64(zhaoliying) # 不是特别安全
方案3:?code=base64(1016_zhaoliying) # random_name,相对比较安全
http://127.0.0.1:7000/dadashop/templates/active.html?code=sdfaefsses
问题:随机数存储在哪里?
3天有效期:Redis
django-redis
django_redis 使用
只支持set、get、delete等基础的命令,但value可以嵌套,复杂的数据类型可以如下操作,但一般用不到
from django_redis import get_redis_connection
r = get_redis_connection()
r.lpush("user",01)
配置setting.py
#dashopt/settings.py
...
CACHES = {
# 缓存邮件激活随机数,可以设置多个default字典,使用时用caches调用
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://192.168.1.11:6379/1",
# "TIMEOUT": None, 默认是300s,可设置,none永久有效
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "123456"
}
},
}
测试
> pip3 install django_redis -i https://pypi.tuna.tsinghua.edu.cn/simple/
> python manage.py shell
>>> from django.core.cache import cache #cahe 默认 default
>>> cache.set("zhaoliying",1016)
True # redis 里1号库查看验证,默认过期的事件是300s
>>> cache.get("zhaoliying")
1016
>>> from django.core.cache import caches #使用caches调用
>>> caches["default"].set("zhaoliying",1016)
设计邮件发送
#user/views.py
import base64
import random
...
from django.core.cache import caches
from django.core.mail import send_mail
CODE_MSG = caches["default"]
def users(request):
....
# 考虑数据库并发
....
# 发送激活邮件,二次开发,try一下
try:
# http://127.0.0.1:7000/dadashop/templates/active.html?code=xxx
# code: base64(1016_username)
code_num = "%d" % random.randint(1000, 9999)
code_str = "%s_%s" % (code_num, uname)
code = base64.urlsafe_b64encode(code_str.encode()).decode()
# 存入redis[key-value] email_active_username
key = "email_active_%s" % uname
CODE_MSG.set(key, code_num, 3600*24*3)
# 生成激活链接
verify_url = "http://127.0.0.1:7000/dadashop/templates/active.html?code=%s" % code
# 发邮件
send_active_email(email, verify_url)
except Exception as e:
print("send active email error %s" % e)
# 签发token[默认1天]
...
def send_active_email(email, verify_url):
"""
功能函数: 发送激活邮件
:param email: 收件人
:param verify_url: 激活链接
:return:
"""
subject = "达达商城激活邮件"
html_message = """
尊敬的用户您好,请点击激活链接进行激活
<a href="%s" target="_blank">点击此处</a>""" % verify_url
send_mail(subject,'',"1234567@qq.com",[email],html_message=html_message)
#dashopt/settings.py
#############发送邮件配置开始############
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 固定写法
EMAIL_HOST = 'smtp.qq.com' # 腾讯QQ邮箱 SMTP 服务器地址
EMAIL_PORT = 25 # SMTP服务的端口号
EMAIL_HOST_USER = '1234567@qq.com' # 发送邮件的QQ邮箱
EMAIL_HOST_PASSWORD = 'qtnCFRZlfysvbcdj' # 在QQ邮箱->设置->帐户->“POP3/IMAP......服务” 里得到的在第三方登录QQ邮箱授权码
EMAIL_USE_TLS = True # 与SMTP服务器通信时,是否启动TLS链接(安全链接)默认false
#############发送邮件配置结束############
http://localhost:7000/dadashop/templates/index.html #注册 查看邮件接收
设计邮件激活
前端对接分析
点击激活连接
http://127.0.0.1:7000/dadashop/templates/active.html?code=NTg1NV91c2VyMDQ=
请求到 http://127.0.0.1:8000/v1/users/activation?code=NTg1NV91c2VyMDQ=
...
<script>
var url_params = location.search
var isBtn = document.getElementsByClassName('active_true')[0];
isBtn.onclick=function(){
$.ajax({
url: baseUrl+'/v1/users/activation'+url_params,
//异步请求,baseUrl在init.js里定义,url_params是上面的变量
type: 'get',
dataType: "json",
success: function (d) {
if (d.code == 200) {
alert('激活成功!')
window.location.href="index.html"
}else{
alert(d.error)
}
}
})
}
</script>
路由设计
#dashopt/urls.py
from django.urls import path, include
# 邮件激活:/v1/users/
path('v1/users/', include('user.urls')),
#user/urls.py
from django.urls import path
from . import views
urlpatterns = [
# 注册邮件激活:/v1/users/activation
path('activation', views.active_view),
]
#user/views.py
def active_view(request):
"""
邮件激活视图逻辑
1.获取前端传过来的code[GET get]
2.校验code[和redis中存储的随机数对比]
key: email_active_username
3.更新该用户is_active=True[UserProfile]
4.清除Redis中该用户数据[delete(key)]
5.组织数据返回[查看接口文档]
"""
pass
功能实现
#user/views.py
...
def active_view(request):
"""
邮件激活视图逻辑
1.获取前端传过来的code[GET get]
2.校验code[和redis中存储的随机数对比]
key: email_active_username
3.更新该用户is_active=True[UserProfile]
4.清除Redis中该用户数据[delete(key)]
5.组织数据返回[查看接口文档]
"""
code = request.GET.get("code")
# code: 由前端url参数传递过来,NzM2Ml9ndWxpbmF6aGE=
if not code:
return JsonResponse({"code": 10102, "error": "not code"})
code_str = base64.urlsafe_b64decode(code.encode()).decode()
# code_str: 1016_zhaoliying
user_num, username = code_str.split('_')
# Redis校验
key = "email_active_%s" % username
redis_num = CODE_MSG.get(key)
if not redis_num:
return JsonResponse({"code": 10103, "error": "The code error"})
if user_num != redis_num:
return JsonResponse({"code": 10104, "error": "The code error"})
# orm更新
try:
user = UserProfile.objects.get(username=username, is_active=False)
except Exception as e:
print("active error is %s" % e)
return JsonResponse({"code": 10105, "error": "The username error"})
user.is_active=True
user.save()
# 清除缓存
CODE_MSG.delete(key)
# 组织数据返回 接口文档
return JsonResponse({"code":200, "data": "激活成功"})
验证
注册用户,先不激活,查看数据库的is_active信息0,邮件激活,再次查看is_active
celery异步网络框架
注意到注册提交时有些微的卡顿,因为:需要等待发送邮件,产生阻塞,考虑使用异步
pip3 install -U Celery -i https://pypi.tuna.tsinghua.edu.cn/simple/
配置
#dashopt/celery.py
from celery import Celery
from django.conf import settings
import os
# 为celery设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dashopt.settings')
# 创建应用
app = Celery("dashop", broker='redis://:123456@192.168.1.11:6379/2')
# 设置app自动加载任务
app.autodiscover_tasks(settings.INSTALLED_APPS)
#user/tasks.py
from dashopt.celery import app
from django.core.mail import send_mail
@app.task
def async_send_active_email(email, verify_url):
"""
功能函数: 发送激活邮件
:param email: 收件人
:param verify_url: 激活链接
:return:
"""
subject = "达达商城激活邮件"
html_message = """
尊敬的用户您好,请点击激活链接进行激活
<a href="%s" target="_blank">点击此处</a>""" % verify_url
send_mail(subject,'',"951699058@qq.com",[email],html_message=html_message)
#user/views.py
from .tasks import async_send_active_email
...
# # 发邮件 改成celery
# send_active_email(email, verify_url)
# celery异步发邮件
async_send_active_email.delay(email, verify_url)
except Exception as e:
print("send active email error %s" % e)
celery启动测试
\dashopt> celery -A dashopt worker -l info
ERROR/MainProcess] Task handler raised error: ValueError('not enough values to unpack (expected 3, got 0)')
#报错处理
#> pip3 install eventlet
#> celery -A dashopt worker -l info -P eventlet
访问测试 http://127.0.0.1:7000/dadashop/templates/index.html,注册,查看celery日志,邮件接收
celery日志
...] Task user.tasks.async_send_active_email[36901e74-6927-45d9-8887-c74a537b5af4] received
...] Task user.tasks.async_send_active_email[36901e74-6927-45d9-8887-c74a537b5af4] succeeded in 1.5939999998081475s: None