pt34user项目一

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必填
email邮箱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" >用户名长度在611位之间</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">密码长度在612位之间</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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值