一、RESTful设计风格
基础概念
全称:Representational State Transfer
1.资源
网络上的一个实体,每个资源都有一个独一无二的URL与之对应;获取资源-直接访问URL即可
2.表现层
资源的表现形式 如HTML、xml、JPG、json等
3.状态转化
访问一个URL即发生一次客户端和服务端得交互;此次交互将会涉及到数据和状态得变化
客户端需要通过某些方式接触具体得变化 如GET、POST、PUT、PATCH、DELETE
设计原则
1.协议 - http/https
2.域名
域名中体现出api字样
https://api.example.com/v1 或 https://example.org/api/.
3.版本
https://api.example.com/v1
4.路径
路径中避免使用动词,资源用名词表示
5.HTTP动词语义
GET、POST、PUT、PATCH、DELETE
示例
6.巧用查询字符串
7.状态码
1)用HTTP响应码表达
2)自定义内部code进行响应
{’code‘:'00000','msg':'success','data':{}}
二、用户系统 - ORM
model
class UserProfile(models.Model):
username=models.BigAutoField(verbose_name="用户名",primary_key=True)
nickname=models.CharField(max_length=20,verbose_name="昵称")
password=models.CharField(max_length=32)
email = models.EmailField()
phone=models.CharField(max_length=11)
avatar=models.ImageField(upload_to='avatar',null=True)
sign=models.CharField(max_length=50,verbose_name="个人签名",default=default_sign)
info=models.CharField(max_length=150,verbose_name="个人简介",default='')
created_time=models.DateTimeField(auto_now_add=True)
updated_time=models.DateTimeField(auto_now=True)
class Meta:
db_table = 'user_user_profile'
三、用户系统-注册
只处理后端
from models import UserProfile
import hashlib
# 数据校验 前后端都要做
class UserViews(APIView):
def post(self,request):
username = request.data['user']
nickname = request.data['nick']
email = request.data['email']
password_1 = request.data['password_1']
password_2 = request.data['password_2']
phone = request.data['phone']
# 参数基本检查
if password_1 != password_2:
return Response("密码不一致")
# 用户名可不可用
old_users = UserProfile.objects.filter(username=username)
if old_users:
return Response("用户名已被使用")
# 插入数据(MD5)
p_m = hashlib.md5()
p_m.update(password_1.encode())
UserProfile.objects.create(username=username,nickname=username,password=p_m.hexdigest(),email=email,phone=phone)
return Response("注册成功")
四、用户系统-登录
views
class LoginViews(APIView):
def post(self,request):
username = request.data['user']
password = request.data['psd']
user_info = UserProfile.objects.filter(username=username)
if not user_info:
return Response("用户不存在")
p_m = hashlib.md5()
p_m.update(password.encode())
if p_m.hexdigest() != user_info.first().password:
return Response("密码不正确")
return Response("登录成功")
五、用户系统-jwt
1.回顾-会话状态的保持
cookies
将会话数据存储到浏览器上独立空间,浏览器每次给网站发请求时,如果检测出当前在cookie区域里有站点的数据,就会自动提交到服务器上。
session
将会话数据存在服务器上(Django存在数据库中),需要借助cookies,通过sessionId检测
2.jwt前传-base64
防君子不防小人
3.jwt前传-HMAC-SH256
第一个参数 加密的key,bytes类型
第二个参数 欲加密的串,bytes类型
第三个参数 hmac的算法,指定为SHA256
import hmac
h = hmac.new(key,str,digestmod='SHA256')
h.digest()
4.JWT-json-web-token
JWT-三大组成
header
格式:
alg代表要使用的算法
typ表明token的类别 - 必须大写JWT
{'alg':'HS256','typ':'JWT'}
payload 分为公有声明和私有声明
公有声明:提供内置关键字用于描述常见的问题,均可选
私有声明:可添加自定义的key 如用户名
signature 根据header中的alg确定具体算法
5.JWT-校验jwt规则
1)解析header,确认alg
2)签名校验-根据传过来的header和payload按alg指明的算法进行签名,将签名结果和传过来的sign进行对比,若对比一致,则校验通过
3)获取payload自定义内容
6.Pyjwt
安装pyjwt(pip3 install)
encode(payload,key,algorithm)
payload:这里是字典,需添加公有声明和私有声明
key:自定义的加密key
algorithm:需要使用的加密算法
decode(token,key)
token:上面生成的token
key:自定义的加密key
import jwt,time
def makeToken(username,expTime=3600*24):
timeTime = time.time()
key = '123456'
payload = {"username":username,"exp":timeTime+expTime}
return jwt.encode(payload,key,algorithm="HS256")
5.用户系统-修改个人信息
6.装饰器校验jwt
django 提供了一个装饰器 method_decorator,可以将函数的装饰器转换成方法装饰器
from django.utils.decorators import method_decorator
@method_decorator(logging_check)
写一个校验token,传递用户的装饰器,写完后只需在函数前加上述@操作
from django.http import JsonResponse
import jwt
from mydemo import settings
from myapp.models import UserProfile
def logging_check(func):
def wrap(request,*args,**kwargs):
# 获取token
token = request.META.get('HTTP_AUTHOPIZAION')
if not token:
return JsonResponse({'code':403,'error':"Please login again"})
# 校验token
# 校验jwt
try:
res = jwt.decode(token,settings.JWT_TOKEN_KEY)
except Exception as e:
return JsonResponse({'code':403,'error':'Please again'})
# 获取登录用户
username = res['username']
user = UserProfile.objects.get(username=username)
request.myuser = user # 将user传给下面的request
return func(request,*args,**kwargs)
return wrap
7.短信接入
1.浏览器点击发送验证码->后端生成随机码并保存在redis(数据在规定时间内有效)
2.注册创建->后端获取前端输入的验证码
3.两个短信验证码进行比较
后端必须知道验证码是多少
第三方短信平台
发短信业务-需要接入其他平台【第三方平台】,让短信平台帮助我们发送短信,该服务通过是有偿服务
容联+云通讯 https://yuntongxun.com
案例
xxxx1是1 xxxx2是2 xxxx3是3
import datetime
import hashlib
import base64
import requests # 发http/https请求
import json
class YunTongXin():
base_ul = 'https://app.cloopen.com:8883'
def __init__(self,accountSid,accountToken,appId,templateId):
self.accountSid = accountSid
self.accountToken = accountToken
self.appId = appId # 应用id 写死
self.templateId = templateId # 模板id 写死
def get_request_url(self,sig):
self.url = self.base_ul + '/2013-12-26/Accounts/%s/SMS/TemplateSMS?sig=%s'%(self.accountSid,sig)
return self.url
def get_timestamp(self):
# 生成时间戳
return datetime.datetime.now().strftime('%Y%m%d%H%M%S')
def get_sig(self,timestamp):
# 生成业务url中的sig
s = self.accountSid+self.accountToken+timestamp
m = hashlib.md5()
m.update(s.encode())
return m.hexdigest().upper()
def get_request_header(self,timestamp):
# 生成请求头
s = self.accountSid+':'+timestamp
auth = base64.b64encode(s.encode()).decode()
return {
'Accept':'application/json',
'Content-Type':'application/json;charset=utf-8',
'Authorization':auth
}
def get_request_body(self,phone,code):
# 生成请求体
return {
"to":phone,
"appId":self.appId,
"templateId":self.templateId,
"datas":[code,"3"]
}
def get_request_api(self,url,header,body):
res = requests.post(url,headers=header,data=body) #res是一个对象
return res.text # 响应体的内容
def run(self,phone,code):
# 获取时间
timestamp = self.get_timestamp()
sig = self.get_sig(timestamp)
url = self.get_request_url(sig)
header = self.get_request_header(timestamp)
# print(url)
# 生成请求体
body = self.get_request_body(phone,code)
# 发请求
data = self.get_request_api(url,header,json.dumps(body))
return data
if __name__ == '__main__':
yun = YunTongXin('xxxx1','xxxx2','xxxx3',1)
res = yun.run('注册手机',"2341")
print(res)
验证码功能流程
1.前端点击 获取验证码 发送请求到后端
2.后端接到请求后
1.生成随机验证码
2.存储验证码->redis
3.发送验证码
3.注册时,需要提交验证码,并在注册逻辑中对比验证码是否正确
生成4位随机数
random_number = random.randint(0,9999)
four_digit_number = '{:04d}'.format(random_number)
import random
import redis
from tools.sms import YunTongXin
class Sms(APIView):
def post(self,request):
phone = request.data['phone']
random_number = random.randint(0,9999)
four_digit_number = '{:04d}'.format(random_number)
conn = redis.Redis(connection_pool=redis.ConnectionPool(host='127.0.0.1', port=6379,max_connections=1000,decode_responses=True,db=15))
conn.setex(phone,60,four_digit_number)
yun = YunTongXin('xxxx1','xxxx2','xxxx3',1)
res = yun.run(phone,four_digit_number)
if res['statusCode'] == '000000':
return Response('ok')
else:
return Response('false')
class SmsCheck(APIView):
def post(self,request):
phone = request.data['phone']
code = request.data['code']
conn = redis.Redis(connection_pool=redis.ConnectionPool(host='127.0.0.1', port=6379,max_connections=1000,decode_responses=True,db=15))
redis_code = conn.get(phone)
if not redis_code:
return Response('code 过期')
if redis_code != code:
return Response('验证码错误')
return Response('ok')
8.celery
broker - 消息传输的中间件,生产者一旦有消息发送,将发至broker broker可以用【RQ,redis】
blackend - 用于存储消息/任务结果,如果需要跟踪和查询任务状态,则需添加要配置相关
worker - 工作者 - 消费/执行broker中消息/任务的进程
能不能celery的标准
1.有没有阻塞
2.实时反馈的不行
使用 - 创建woker(不使用django)
from celery import Celery
app = Celery('guoxiaonao',
broker='redis://172.3.3.100:6379/5'
)
# 创建任务函数
@app.task
def task_test():
print('task is run ... ')
使用 - 启动woker
在tasks.py文件同级目录下执行
celery -A tasks worker --loglevel=info
使用 - 创建生产者 - 推送任务
在tasks.py 文件的同级目录下进入ipython3执行
from tasks import task_test
task_test.delay()
使用 - 存储执行结果 - work
要想存储执行结果需要添加 backend
django中使用celery
1.创建celery配置文件
项目同名目录下创建celery.py
2.应用下创建tasks.py集中定义队形worker函数
3.视图函数充当生产者,推送具体worker函数
4.项目目录下启动worker
celery -A 项目同名目录名 worker -l info
六、文章系统
模型类
列表页 - 处理
方式1:后端给前端,文章全部内容 前端自己截取
方式2:后端从数据库里获取全部文章内容,截取好后,响应给前端
方式3:数据库冗余一个字段【简介】,后端只去简介字段内容
model 模型类
from django.db import models
# Create your models here.
class Topic(models.Model):
title = models.CharField(max_length=50,verbose_name="文章标题")
category = models.CharField(max_length=20,verbose_name="文章分类")
limit = models.CharField(max_length=20,verbose_name="文章权限")
introduce = models.CharField(max_length=90,verbose_name="文章简介")
content = models.TextField(verbose_name="文章内容")
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
发表文章
就是获取前端数据,将数据存入数据库 无技术可言
列表页
游客访问只能看到公共的文章,博主自己能访问自己的私有文章
详情页
在列表页点击文章标题进入文章详情页面
获取用户文章的上一篇
当前文章id 1
select * from 表 where id > 1 and people = gxn order by id asc limit 1
下一篇
当前文章id 4
select * from 表 id < 4 and people = gxn order by id desc limit 1
文章缓存
典型读多写少
缓存方案一:cache_page(过期时间s)
有点:
缺点:
1.无法按照具体的方可身份,进行针对性的存储,
例如:存储的是博主访问自身博客的数据,方可到访是可能会读到 博主删除的缓存
2.删除缓存成本太高【出现新旧数据不一致】
缓存方案二:局部 - cache.set/get
有点:灵活、存储成本最有
缺点:代码实现成本高
装饰器缓存
判断是否在redis中缓存 如果有直接retrun 没有执行视图然后存储缓存
res = fun(request,*args,**kwargs) 去执行视图函数的内容
from django.http import HttpResponse
def cache_set(expire):
def _cache_set(fun):
def wrapper(request,*args,**kwargs):
print(args[0])
print(args[0].path_info)
print(args[0].user)
aa = kwargs['pk']
if aa == 111:
# 如果aa == 111 执行视图
res = fun(request,*args,**kwargs)
return res
else:
return HttpResponse(aa)
return wrapper
return _cache_set
留言功能
采用创建父项id的方式实现
字段名 | 类型 | 作用 | 备注1 | 备注2 |
id | int | 主键自增 | 无 | 无 |
content | varchar(50) | 留言内容 | 无 | 无 |
created_time | date | 留言创建时间 | 无 | 无 |
parent_message | int | 该留言的父留言,此ID若存在证明该留言为回复 | int | 无 |
publisher_id | varchar(10) | 留言的发布者 | user_profile外键 | 无 |
topic_id | varchar(10) | 文章 | topic 外键 | 无 |
#关联留言和回复
all_messages = Message.objects.filter(topic=author_topic).order_by('-created_time')
msg_list = []
rep_dic = {}
m_count = 0
for msg in all_messages:
if msg.parent_message:
#回复
rep_dic.setdefault(msg.parent_message, [])
rep_dic[msg.parent_message].append({'msg_id':msg.id,'publisher':msg.publisher.nickname, 'publisher_avatr':str(msg.publisher.avatar),'content':msg.content, 'created_time':msg.created_time.strftime('%Y-%m-%d %H:%M:%S')})
else:
#留言
m_count += 1
msg_list.append({'id':msg.id, 'content':msg.content,'publisher':msg.publisher.nickname, 'publisher_avatar':str(msg.publisher.avatar),'created_time':msg.created_time.strftime('%Y-%m-%d %H:%M:%S'), 'reply':[]})
for m in msg_list:
if m['id'] in rep_dic:
m['reply'] = rep_dic[m['id']]