Django博客项目实战
一、项目简介
-
项目名称:久至博客
-
开发模式:前后端分离模式
-
主体功能:
1)用户功能
2)文章功能
3)留言功能
4)支付功能
二、前后端分离
定义及优缺点
- 前端:即客户端,负责渲染用户显示界面【如web的js动态渲染页面,安卓,IOS,PC客户端等】
- 后端:即服务器端,负责接收http请求,处理数据
- API:Application Programming Interface 是一些预先定义的函数,或指软件系统不同组成部分衔接的约定
请求过程
前后端分离完整请求过程
- 1.前端通过http请求后端API
- 2.后端以json形式返回前端数据
- 2.前端生成用户显示界面【如html,ios,android】
优点
-
各司其职:
前端:视觉层面,兼容性,前端性能优化
后端:并发,可用性,性能
-
解耦,前端和后端均易于扩展
-
后端灵活搭配各类前端 - 如安卓等
-
提供用户体验
-
前端+后端可完全并行开发,加快开发效率
分离常见问题
问题 | 解决方案 |
---|---|
解决HTTP无状态? | 采用token(令牌) |
前端为JS,如何解决跨域问题? | 采用CORS |
如何解决CSRF问题 | 采用token |
是否会影响Search Engine Optimization(SEO)效果 | 会,前后端分离后,往往页面不存在静态文字【例如新闻的详细内容】 |
逻辑是由前端还是后端完成? | 底线原则:数据校验需要前后端都做 |
前端工作压力大 | 团队协作 |
动静分离和前后端分离的区别 | 动静分离是指 css/js/img静态资源 和服务器 拆开部署,典型方案-静态资源交由CDN厂商处理【蓝汛 网宿 阿里云 腾讯云】 |
判别标准
判断前后端分离的核心标准:谁生成显示页面
- 1.后端生成【前后端未分离】 ex:flask -> render_template django -> HttpResponse(html)
- 2.前端生成【前后端分离】
跨域(CORS)
跨域资源共享(Cross-origin resource sharing)
允许浏览器向跨源(协议+ 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
特点:
1.浏览器自动完成(在请求头中加入特殊头 或 发送特殊请求)
2.服务器需要支持(响应头中需要有特殊头)
跨域请求-简单请求
判断标准
满足以下全部条件的请求为 简单请求
-
1.请求方法如下
GET or HEAD or POST
-
2.请求头仅包含如下
Accept / Accept-Language / Content-Language / Content-Type
-
3.Content-Type仅支持如下三种
application/x-www-form-urlencoded / multipart/form-data / text/plain
跨域流程
-
1.请求
请求头中携带 Origin,该字段表明自己来自哪个域
-
2.响应
如果服务器不接受此请求域,则响应头中不包含 Access-Control-Allow-Origin
如果请求头中的Origin在服务器接受范围内,则返回如下头
响应头 | 作用 | 备注 |
---|---|---|
Access-Control-Allow-Origin | 服务器接受的域 | |
Access-Control-Allow-Credentials | 是否接受Cookie | 可选 |
Access-Control-Expose-Headers | 默认xhr只能拿到如下响应头:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified;如果有需要获取其他头,需在此指定 | 可选 |
预检请求跨域(大部分)
不满足简单请求的条件则为预检请求
跨域流程
- 1.OPTIONS 请求发起,携带如下请求头
请求头 | 作用 | 备注 |
---|---|---|
Origin | 表明此请求来自哪个域 | 必选 |
Access-Control-Request-Method | 此次请求使用方法 | 必选 |
Access-Control-Request-Headers | 此次请求使用的头 | 必选 |
- 2.OPTIONS接受响应阶段,携带如下响应头
响应头 | 作用 | 备注 |
---|---|---|
Access-Control-Allow-Origin | 服务器接受的域 | 必选 |
Access-Control-Allow-Methods | 告诉浏览器,服务器接受的跨域请求方法 | 必选 |
Access-Control-Expose-Headers | 返回所有支持的头部,当request有’Access-Control-Request-Headers’时,该响应头必然回复 | 必选 |
Access-Control-Allow-Credentials | 默认xhr只能拿到如下响应头:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified;如果有需要获取其他头,需在此指定 | 可选 |
Access-Control-Max-Age | OPTION请求缓存时间,单位s,一般设为1天 | 可选 |
- 3.主请求阶段
作用 | 请求头 | 备注 |
---|---|---|
表明此请求来自哪个域 | Origin | 必选 |
- 4.主请求响应阶段
响应头 | 作用 | 备注 |
---|---|---|
Access-Control-Allow-Origin | 服务器接受的域 |
cors安装与配置
sudo pip3 freeze|grep -i 'cors' # 查看是否安装
sudo pip3 install django-cors-header # 安装
在django项目中setting.py 配置
# 1.注册应用中 添加 corsheaders
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
]
# 2.中间件中 添加 'corsheaders.middleware.CorsMiddleware'
#位置尽量靠前,在其上方 'django.middleware.common.CommonMiddleware'
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# 3.允许所有请求域 为True(开发测试阶段)时,白名单则不启用
CORS_ORIGIN_ALLOW_ALL = True
# 4.允许请求域 白名单列表(正式上线)
CORS_ORIGIN_WHITELIST = []
# 5.方法名列表
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
# 6.响应头列表
CORS_ALLOW_HEADERS = (
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)
# 7.预检请求 请求缓存时间,默认为86400
CORS_PREFLIGHT_MAX_AGE = 86400
# 8.希望AJAX接收特殊的响应头,默认响应头已满足
CORS_EXPOSE_HEADERS =[]
# 9.是否要跨域的Cookie,默认False
CORS_ALLOW_CREDENTIALS = False
三、RESTful设计风格
简介
全称:Representational State Transfer
-
1.资源(Resources)
网络上的一个实体,或者说是网络上的一个具体信息,并且么每个资源都有一个独一无二的URL与之对应;获取资源直接访问URL即可
-
2.表现层(Representation)
资源的表现形式;如HTML、xml、JPG、json等
-
3.状态转化(State Transfer)
访问一个URL即发生了一次客户端和服务端的交互,此次交互将会涉及到数据和状态的变化;客户端需要通过某些方式触发具体的变化 - HTTP method,如GET、POST、PUT、PATCH、DELETE 等
特征
- 每个URL代表一种资源
- 客户端和服务器端之间传递着资源的某种表现
- 客户端通过HTTP的几个动作对资源进行操作 - 发生状态转化
设计原则
-
1.协议 - http/https
-
2.域名:域名中体现出 api 字样,如 https//api.example.com or https:///example.org/api/
-
3.版本:https://api.example.com/V1/
-
4.路径 :路径中避免使用动词,资源用 名词 表示
https://api.example.com/v1/users
https://api.example.com/v1/animals
-
5.HTTP动词语义
GET (SELECT):从服务器取出资源(一项或多项)
POST(CREATE):在服务器新建一个资源
PUT(UPDATE):在服务更新资源(客户端提供改变后的完整资源)
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
DELETE(DELETE):从服务器删除资源
GET /schools # 列出所有学校 POST /schools # 新建一所学校 GET /schools/ID # 获取指定ID的学校信息 PUT /schools/ID # 更新指定ID的学校信息(提供该学校的全部信息) PATCH /schools/ID # 更新指定ID的学校信息(提供该学校的部分信息) DELETE /schools/ID # 删除指定学校 GET /schools/ID/classes # 列出指定ID的学校的所有班级 DELETE /schools/ID/classes/ID # 删除指定学校的指定班级
-
6.巧用查询字符串
?limit=10 # 指定返回记录的数量 ?offset=10 # 指定返回记录的开始位置 ?page=2&per_page=100 # 指定第几页,以及每页的记录数 ?sortby=name&order=asc # 指定返回结果按照哪个属性排序,以及排序顺序 ?type_id=1 # 指定筛选条件
-
7.状态码
1)用HTTP响应码表达 此次请求结果
200 OK - [GET] # 服务器成功返回用户请求的数据 201 CREATED -[POST/PUT/PATCH] # 用户新建或修改数据成功 202 Accepted -[*] # 表示一个请求已经进入后台排除(异步任务) 204 NO CONTENT - [DELETE] # 用户删除数据成功 400 INVALID REQUEST - [POST/PUT/PATCH] # 用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的 401 Unauthorized - [*] # 表示用户没有权限(令牌、用户名、密码错误)
2)自定义内部code进行响应
如:返回结构如下 {‘code’ : 200, ‘data’ : {}, ‘error’ : ‘xxx’}
-
8.返回结果: 根据HTTP动作的不同,返回结果的结果也有所不同
GET /users # 返回资源对象的列表(数组) GET /users/zhangsan # 返回单个资源对象 POST /users # 返回新生成的资源对象 PUT /users/zhangsan # 返回完整的资源对象 PATCH /users/zhangsan # 返回完整的资源对象 DELETE /users/zhangsan # 返回一个空文档·
四、用户系统
视图写法
# FBV 函数视图
def user_views(request):
if request.method == 'GET':
pass
elif request.method == 'POST':
pass
# CBV 类视图,继承Django的View
"""优点: 更灵活【可继承】
对未定义的http method请求,直接返回405响应
"""
from django.views import View
class UserViews(View):
def get(self,request):
pass
def post(self,request):
pass
# 绑定类视图的url主路由
from django.contrib import admin
from django.urls import path
from user import views as user_views
urlpatterns = [
path('admin/', admin.site.urls),
# 可以在结尾省去 / ,绑定类时,需要.as_view()
path('v1/user1',user_views.UserViews.as_view())
]
加密方法
base64
import base64
b64encode # 转为base64规则的串
b64decode # 解密
urlsafe-b64encode # base64加密,但会将 '+' 替换成 '-',将 '/' 替换成 '_'
urlsafe-b64decode # 解密
HMAC-SHA256
- 是一种通过特别计算方式之后产生的消息认证码,使用散列散发同时结合一个加密秘钥。
- 用来保证数据的完整性,也可用来做某个消息的身份验证
import max
h = hmax.new(key,str,digestmod ='SHA256')
h.digest() # 获取结果
""" 生成hmax对象
key 为加密秘钥(可自定义),bytes类型,
str 为需加密的串,bytes类型
digestmod 为hmac的算法,指定为 SHA256
"""
会话状态保持
方式一:Cookies和Session
-
Cookies
将会话数据存储在浏览器上,浏览器每次给站点发请求自动将Cookies数据提交至服务器
-
session
将会话数据存储在服务器上(Django存储在数据库中),需要借助浏览器的Cookies
方式二:JWT(令牌)
定义
- json web token(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准
- JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源
三大组成
1.header
格式为字典,需要转成json串并用base64转码
{'alg':'HS256','typ':'JWT'}
""" alg 代表要使用的 算法
typ 表明该token的类别 - 此处必须为 大写的 JWT
"""
2.payload
- 格式为字段,分为公有声明和私有声明
- 公共声明和私有声明均在统一字典中,转成json串,并用base64转码
- 公有声明:JWT提供了内置关键字 用于描述常见的问题,均为可选项,可根据需求添加key,常用声明如下:
{ 'exp':xxx , # Expiration Tiem 此token的过期时间的时间戳
'iss':xxx , # (Issuer) Claim 指明此token的签发者
'iat':xxx , # (Issued At) Claim 指明此创建时间的时间戳
'aud':xxx , # (Audience) Claim 指明此token签发面向群体(IOS,Android等)
}
- 私有声明:用户可根据自己的业务需求,添加自定义的key
{'username':'testuser'}
3.signature
根据header中的alg确定具体算法,以下用HS256为例
HS256(自定义的key,base64后的header + ‘.’ + base64后的payload)
校验规则
- 解析header,确认alg
- 签名校验 - 根据传过来的 header 和 payload 按 alg 指明的算法进行签发,将签名结果 和 传过来的 sign 进行对比,若对比一致,则校验通过
- 获取payload自定义内容
pyjwt安装与使用
- 安装JWT
pip3 freeze|grep -i 'jwt' # 检验是否安装
pip3 install pyjwt # 安装
-
JWT签发后,交由浏览器保存
# token校验key () JWT_TOKEN_KEY = 'longto' # 记录token信息,并携带返回 import jwt token = make_token(username) result = {'code':200,'username':username,'data':{'token':token.decode()}} return JsonResponse(result) # 使用JWT生成token def make_token(username,expire=3600*24): key =settings.JWT_TOKEN_KEY cur_time = time.time() payload_data = {'username':username,'exp':cur_time+expire} return jwt.encode(payload_data,key,algorithm='HS256')
-
浏览器可将其存储在 "本地存储"中
# 在html的 Ajax中存储 success: function(result){ if (result.code == 200){ window.localStorage.setItem('dnblog_token',result.data.token) window.localStorage.setItem('dnblog_username',result.username) alert('登录成功') }else{ alert(result.error) } }
-
需要 用户登录 才能使用的功能,前端ajax中需要将jwt传至后端,可放在请求头中发送
$.ajax({ processData: false, contentType: false, url: url, type: 'post', data: formdata, // 发送请求前,将token加入请求头 beforeSend: function(request) { request.setRequestHeader("Authorization", token); }, success: function(arg) { if (arg.code == 200) { alert('成功!') window.location.reload() } else { alert('失败!') } } }) }
验证登录状态
-
使用装饰器进行验证,但是一个装饰器无法同时在函数视图和类视图中使用?
-
django提供了一个装饰器 method_decorator, 可以将函数装饰器转换成 方法装饰器
# 在 函数视图 使用 @logging_check def FBV(request) pass # 在 类视图方式 使用 class CBV(View): @method_decorator(logging_check) def put(self,request): pass # 定义一个装饰器 def logging_check(func): def wrap(request,*args,**kwargs): # 获取token 请求头中封装会全部加上 HTTP_,并大写 token = request.META.get('HTTP_AUTHORIZATION') if not token: result = {'code': 403, 'error': '登录状态失效,请重新登录'} return JsonResponse(result) # 校验token try: key = settings.JWT_TOKEN_KEY payloads = jwt.decode(token,key) print('payloads is %s'%(payloads)) except Exception as e: # 失败返回 print('jwt decode error is %s' % (e)) result = {'code':403,'error':'登录状态失效,请重新登录'} return JsonResponse(result) # 获取登录用户 username = payloads['username'] user = UserProfile.objects.get(username=username) request.myuser=user return func(request,*args,**kwargs) return wrap
短信接入
- 发短信业务需要第三方短信平台,帮助我们发送短信,该服务通常为有偿服务
- 容联*云通讯 - https://www.yuntongxun.com/
- 接入文档 https://doc.yuntongxun.com/p/5a531a353b8496dd00dcdfe2
# 参考接入文档,形成的发送短信类
import base64
import datetime
import hashlib
import json
import requests
class YunTongXun():
base_url = 'https://app.cloopen.com:8883'
def __init__(self,accountSid,accountToken,appId,templateId):
# 账户ID
self.accountSid = accountSid
# 授权令牌
self.accountToken = accountToken
# 应用id
self.appId = appId
# 模板id
self.templateid = templateId
def get_request_url(self,sig):
self.url = self.base_url+'/2013-12-26/Accounts/%s/SMS/%s?sig=%s'%(self.accountSid,'TemplateSMS',sig)
return self.url
def get_TimeStamp(self):
# 生成时间戳
return datetime.datetime.now().strftime('%Y%m%d%H%M%S')
def get_SigParameter(self,timeStamp):
# 生成业务url中的sig
str = self.accountSid+self.accountToken+timeStamp
hmd5 = hashlib.md5()
hmd5.update(str.encode())
return hmd5.hexdigest().upper()
def get_Header(self,timeStamp):
s = self.accountSid+':'+timeStamp
auth = base64.b64encode(s.encode()).decode()
return {
'Accept':'application/json',
'Content-Type':'application/json;charset=utf-8',
'Content-Length':'Content-Length',
'Authorization':auth
}
def get_request_body(self,phone,code):
return {
'to':phone,
'appId':self.appId,
'templateId' : self.templateid,
'datas':[code,'3']
}
def request_api(self,url,header,body):
res=requests.post(url,headers=header,data=body)
return res.text
def run(self,phone,code):
# 获取时间戳
timeStamp = self.get_TimeStamp()
# 生成签名
sig = self.get_SigParameter(timeStamp)
# 生成业务url
url = self.get_request_url(sig)
# print(url)
# 生成请求头
header = self.get_Header(timeStamp)
# print(header)
# 生成请求体
body = self.get_request_body(phone,code)
# 发请求
data = self.request_api(url,header,json.dumps(body))
return data
if __name__ == '__main__':
config = {
'accountSid':'8aaf07087f77bf96017f87a6c21d0327',
'accountToken':'55cdb5e7e0f74b13b96e76f32148a0aa',
'appId':'8aaf07087f77bf96017f87a6c326032e',
'templateId':'1'
}
yun = YunTongXun(**config)
res = yun.run('15558703665','991207')
print(res)
验证码功能流程
-
1.前端页面 点击 “免费获取验证码”,发送Ajax 请求到后端
-
2.后端接到请求后
生成随机验证码
存储验证码(redis)
发送验证码
-
3.注册时,需要提交验证码,并在注册逻辑中对比验证码是否正确
安装django-redis
pip3 freeze|grep -i 'redis'
sudo pip3 install django-redis
settings.py配置
# redis缓存连接
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD" :"123456"
}
}
}
django-redis使用方式
# 方式1 具备序列化和反序列化
cache.set/get
# 方式2
from django_redis import get_redis_connection
r = get_redis_connection()
# 方式3 装饰器
@cache_page(60)
Celery异步
-
Celery是一个简单、灵活且可靠的,处理大量消息的分布式系统
-
他是一个专注于实时处理的任务队列,同时也支持任务调度
-
中文官网:http://docs/jinkan.org/docs/celery/
pip3 freeze|grep -i 'celery' # 检验 sudo pip3 install Celery # 安装
-
Broker - 消息中间件,消息传输的中间件,生产者将消息发至broker【RQ,redis】
-
worker - 工作者 -消费/执行broker中的消息/任务的进程
-
backend - 结果存储- 用于存储消息/任务结果,如果需要跟踪和查询任务状态,则需要添加相关配置
如何判断是否需要业务是否需要使用Celery(异步)?
- 1.业务是否会发生阻塞
- 2.业务无法实时获取结果,只需快速响应
celery基础配置与使用
- 创建worker,tasks.py文件
# Celery提供存储任务执行结果方案,需借助redis或mysql或Memcached等
from celery import Celery
app = Celery('user_celery',
broker='redis://:[email protected]:6379/1',
backend='redis://:[email protected]:6379/2',
)
# 无密码测试样例
"""app = Celery('user_celery',
broker='redis://:@127.0.0.1:6379/1',
backend='redis://:[email protected]:6379/2',)
"""
# 创建任务函数,必须加上装饰器
@app.task
def task_test(a,b):
print("task is running...")
return a+b
# 注意任务函数返回结果将存储到 配置的数据库中
-
启动worker
Ubuntu 终端中,tasks.py文件同级目录下 执行
celery -A tasks worker --loglevel=info
或简写 celery -A tasks worker -l info
此模式默认为前台启动,终端中会输出相关日志
-
创建生产者 -推送任务
# 在tasks.py 文件的 同级目录下 进入 ipython3 执行如下代码 from tasks import task_test task_test.delay() # 执行完毕后,观察 worker日志即可
Django中使用celery
-
1.创建celery配置文件 项目同名目录下创建 celery.py
from celery import Celery from django.conf import settings import os os.environ.setdefault('DJANGO_SETTINGS_MODULE','longtoblog.settings') # 写法一: app = Celery('longtoblog') app.conf.update( BROKER_URL ='redis://:[email protected]:6379/1', BACKEND_URL ='redis://:[email protected]:6379/2',+ ) # 写法二: app = Celery('user_celery', broker='redis://:[email protected]:6379/1', backend='redis://:[email protected]:6379/2', ) # 自动去注册应用下 寻找加载 worker函数 app.autodiscover_tasks(settings.INSTALLED_APPS)
-
2.应用下创建 tasks.py 集中定义对应的 worker 函数
from django.conf import settings from longtoblog.celery import app from tools.sms import YunTongXun @app.task def sms_send_celery(phone,code): config = { 'accountSid': settings.SMS_ACCOUNTSID, 'accountToken': settings.SMS_ACCOUNTTOKEN, 'appId': settings.SMS_APPID, 'templateId': settings.SMS_TEMPLATEID } yun = YunTongXun(**config) res = yun.run(phone, code) return res
-
3.视图函数充当生产者,推送具体的worker函数
# 发送随机码 -> 短信 # res = sms_send(phone,code) # celery版本 sms_send_celery.delay(phone,code)
-
4.项目目录下启动 worker
celery -A 项目同名目录名 worker -l info (终端界面-前台运行)
celery -A longtoblog worker -l info #(终端界面-前台运行)
# 正式环境后台启动celery worker
nohup celery -A project worker -P gevent -c 1000 > celery.log 2>&1 &
""" #1, nohup: 忽略所有挂断(SIGHUP)信号
#2, 标准输入是文件描述符 0。他是命令的输入,缺省是键盘,也可以是文件或其他命令的输出
#标准输出时文件描述符 1。它是命令的输出,缺省是屏幕,也可以是文件。
#标准错误时文件描述符 2。它是命令错误的输出,缺省是屏幕,也可以是文件。
#3, &符号:代表将命令在后台执行
"""
五、文章系统
缓存问题 - 文章列表页
缓存 - 把高频读取的数据,放置到更快的存储介质里
1.cache_page(过期时间s)
- 优点:简单
- 缺点:1.无法按照具体的访客身份,进行针对性的存储,例如,存储的是博主访问自身博客的数据,访客到访时可能会读取到 博主触发的缓存
- 2.删除缓存成本过高【出现新旧数据不一致】
2.局部 - cache.set/get
优点:灵活,存储成本最优,删除成本低
缺点:代码实现成本较高
案例:模型类中,定义 classmethod 方法
class Topic
@classmethod
def get_topic_list(cls,):
if cache:
return cache
data = cls.objects.filter(....)
# cache in
return data
3.结合装饰器与局部缓存方案
优缺点,综合了方案一和方案二,满足需求
# 装饰器中加参数,外层再套一层函数
from django.core.cache import cache
from tools.logging_decorate import get_user_by_request
def cache_set(expire):
def _cache_set(func):
def wrap(request, *args, **kwargs):
# 区分场景 - 只做列表页
if 't_id' in request.GET:
# 当前请求是获取 文章详情页
return func(request, *args,**kwargs)
# 生成出 正确的 cache_key 【访客访问 和 博主访问】
visitor_user = get_user_by_request(request)
visitor_username = None
if visitor_user:
visitor_username = visitor_user.username
author_username = kwargs['author_id']
print('visitor is %s'%(visitor_username))
print('author is %s'%(author_username))
full_path = request.get_full_path()
if visitor_username == author_username:
cache_key = 'topics_cache_self_%s'%(full_path)
else:
cache_key = 'topics_cache_%s' % (full_path)
print('cache key is %s'%(cache_key))
# ---判断是否有缓存,有缓存则直接返回
res = cache.get(cache_key)
if res:
print('---cache in')
return res
# 执行视图
res = func(request, *args, **kwargs)
# 存储缓存 cache对象 /set/get
cache.set(cache_key,res,expire)
# 返回响应
return func(request, *args, **kwargs)
return wrap
return _cache_set
留言功能
涉及留言和回复
# 关联留言和回复
all_messages = Message.objects.filter(topic=author_topic).order_by('-created_time')
message_count =0
msg_list =[]
reply_dic = {}
for msg in all_messages:
if msg.parent_message:
# 回复
reply_dic.setdefault(msg.parent_message,[])
reply_dic[msg.parent_message].append({'msg_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')})
else:
# 留言
message_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':[]})
第三方支付-支付宝
注册开发者账号
调试支付宝支付需要先在 支付宝开放平台 进行注册,入驻为“自助研发者”;
链接为:https://open.alipay.com/platform/home.htm
RSA加密算法(非对称)
RSA公开秘钥密码体制是一种使用不同的加密秘钥与解密秘钥,“由已知加密秘钥推导出解密秘钥在计算上是不可行的”密码体制
RSA钥匙
- 公钥加密/私钥解密
- 私钥签名/公钥验签
生成本地公私钥
# 终端生成
pp@pp:~$openssl
# 导出私钥
OpenSSL>genrsa -out app_private_key.pem 2048
# 导出公钥
OpenSSL>rsa -in app_private_key.pem -pubout -out app_public_key.pem
OpenSSL>exit
pp@pp:~$ls
app_private_key.pem app_publice_key.pem
沙箱添加RSA公钥
- 点击 沙箱应用展示信息页中的 RSA2 密钥的 设置/查看 公钥加密/私钥
-
在弹出的对话框中,选择 公钥 模式,并将 本地生成的 app_public_key.pem中的 内容复制 到框中,并保存复制 支付宝公钥 为 alipay_public_key.pem
-
注意:生成公钥如下,只复制 -----BEGIN PUBLIC KEY ------ 和 -----END PUBLIC KEY-----之间的内容,保存支付宝公钥同理
安装支付宝第三方SDK
# 安装 python-alipay-sdk
sudo pip3 install python-alipay-sdk -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装成功后执行如下命令 校验安装结果
pip3 freeze|grep -i ali
# python-alipay-sdk==3.0.4 输出此结果,则表示安装成功
支付流程
Django使用配置
-
1.setting.py
# 静态文件夹路径 STATICFILES_DIRS = (os.path.join(BASE_DIR,'static'),) # RSA文件(公钥、私钥)路径 ALIPAY_KEY_DIRS = os.path.join(BASE_DIR,'static/key_file/') # 支付宝 APPID、return_url、notify_url 路径配置 ALIPAY_APPID ='2021000119640211' ALIPAY_RETURN_URL = 'http://127.0.0.1:8000/payment/result' ALIPAY_NOTIFY_URL = 'http://127.0.0.1:8000/payment/result'
-
视图views.py
from django.http import JsonResponse, HttpResponse from django.shortcuts import render from django.views import View from django.conf import settings from alipay import AliPay import time app_private_key_string = open(settings.ALIPAY_KEY_DIRS + 'app_private_key.pem').read() alipay_public_key_string = open(settings.ALIPAY_KEY_DIRS + 'alipay_public_key.pem').read() class MyAliPay(View): def __init__(self, **kwargs): super().__init__(**kwargs) self.alipay = AliPay( appid=settings.ALIPAY_APPID, app_private_key_string=app_private_key_string, alipay_public_key_string=alipay_public_key_string, app_notify_url=None, sign_type="RSA2", debug=True #默认False True则将请求转发沙箱环境 ) def get_trade_url(self, order_id, amount): order_string = self.alipay.api_alipay_trade_page_pay( subject=order_id, out_trade_no=order_id, total_amount= amount, #支付完毕后,将用户跳转至哪个页面 return_url=settings.ALIPAY_RETURN_URL, notify_url=settings.ALIPAY_NOTIFY_URL ) return "https://openapi.alipaydev.com/gateway.do?" + order_string def get_verify_result(self, data, sign): #验证签名 True 成功 False 失败 return self.alipay.verify(data, sign) def get_trade_result(self, order_id): #主动查询订单状态 result = self.alipay.api_alipay_trade_query(order_id) # 判断交易状态 if result.get('trade_status') == 'TRADE_SUCCESS': return True return False class OrderView(MyAliPay): def get(self, request): return render(request, 'alipay.html') def post(self, request): #返回支付地址 #接收到文章id后,生成订单 订单状态 待付款 已付款 付款失败 order_id = '%sGXN' %(int(time.time())) pay_url = self.get_trade_url(order_id,999) return JsonResponse({"pay_url":pay_url}) class ResultView(MyAliPay): def post(self, request): #notify_url 业务逻辑 # 所有返回数据 request_data request_data = {k:request.POST[k] for k in request.POST.keys()} sign = request_data.pop('sign') # 取出签名的校验结果 is_verify = self.get_verify_result(request_data, sign) if is_verify is True: #当前请求是支付宝发的 trade_status = request_data.get('trade_status') if trade_status == 'TRADE_SUCCESS': print('----支付成功') #修改自己数据库里的订单状态 例如 待付款 - 已付款 # 按照支付宝异步要求,只能发送 success ,支付成功 return HttpResponse('success') else: return HttpResponse('违法请求') def get(self, request): #return url 业务逻辑 # 获取订单号 out_trade_no order_id = request.GET['out_trade_no'] #查询订单表状态,如果还是待付款,采取B方案 - 主动查询支付宝 订单真实交易状态 #主动查询 result = self.get_trade_result(order_id) if result: return HttpResponse('--支付成功--主动查询') else: return HttpResponse('--支付异常--主动查询')