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':passelif request.method == 'POST':pass# CBV 类视图,继承Django的View
"""优点: 更灵活【可继承】对未定义的http method请求,直接返回405响应
"""
from django.views import View
class UserViews(View):def get(self,request):passdef post(self,request):pass# 绑定类视图的url主路由
from django.contrib import admin
from django.urls import path
from user import views as user_viewsurlpatterns = [path('admin/', admin.site.urls),# 可以在结尾省去 / ,绑定类时,需要.as_view()path('v1/user1',user_views.UserViews.as_view())
]

加密方法

base64

import base64b64encode # 转为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
  • 签名校验 - 根据传过来的 headerpayloadalg 指明的算法进行签发,将签名结果 和 传过来的 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_KEYcur_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)# 校验tokentry:key = settings.JWT_TOKEN_KEYpayloads = 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=userreturn func(request,*args,**kwargs)return wrap
    

短信接入

  • 发短信业务需要第三方短信平台,帮助我们发送短信,该服务通常为有偿服务
  • 容联*云通讯 - https://www.yuntongxun.com/
  • 接入文档 https://doc.yuntongxun.com/p/5a531a353b8496dd00dcdfe2
# 参考接入文档,形成的发送短信类import base64
import datetime
import hashlib
import jsonimport requestsclass YunTongXun():base_url = 'https://app.cloopen.com:8883'def __init__(self,accountSid,accountToken,appId,templateId):# 账户IDself.accountSid = accountSid# 授权令牌self.accountToken = accountToken# 应用idself.appId = appId# 模板idself.templateid = templateIddef 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.urldef get_TimeStamp(self):# 生成时间戳return datetime.datetime.now().strftime('%Y%m%d%H%M%S')def get_SigParameter(self,timeStamp):# 生成业务url中的sigstr = self.accountSid+self.accountToken+timeStamphmd5 = hashlib.md5()hmd5.update(str.encode())return hmd5.hexdigest().upper()def get_Header(self,timeStamp):s = self.accountSid+':'+timeStampauth = 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.textdef run(self,phone,code):# 获取时间戳timeStamp = self.get_TimeStamp()# 生成签名sig = self.get_SigParameter(timeStamp)# 生成业务urlurl = 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 dataif __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://:password@127.0.0.1:6379/1',backend='redis://:password@127.0.0.1:6379/2',)
# 无密码测试样例
"""app = Celery('user_celery',broker='redis://:@127.0.0.1:6379/1',backend='redis://:password@127.0.0.1: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 osos.environ.setdefault('DJANGO_SETTINGS_MODULE','longtoblog.settings')# 写法一:
    app = Celery('longtoblog')
    app.conf.update(BROKER_URL ='redis://:123456@127.0.0.1:6379/1',BACKEND_URL ='redis://:123456@127.0.0.1:6379/2',+
    )
    # 写法二:
    app = Celery('user_celery',broker='redis://:123456@127.0.0.1:6379/1',backend='redis://:123456@127.0.0.1:6379/2',)# 自动去注册应用下 寻找加载 worker函数
    app.autodiscover_tasks(settings.INSTALLED_APPS)
    
  • 2.应用下创建 tasks.py 集中定义对应的 worker 函数

    from django.conf import settings
    from longtoblog.celery import appfrom 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@classmethoddef get_topic_list(cls,):if cache:return cachedata = cls.objects.filter(....)# cache inreturn data

3.结合装饰器与局部缓存方案

优缺点,综合了方案一和方案二,满足需求

# 装饰器中加参数,外层再套一层函数
from django.core.cache import cachefrom tools.logging_decorate import get_user_by_requestdef 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 = Noneif visitor_user:visitor_username = visitor_user.usernameauthor_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/getcache.set(cache_key,res,expire)# 返回响应return func(request, *args, **kwargs)return wrapreturn _cache_set

留言功能

涉及留言和回复

# 关联留言和回复all_messages = Message.objects.filter(topic=author_topic).order_by('-created_time')message_count =0msg_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 +=1msg_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:~$lsapp_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 timeapp_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_stringdef 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 Truereturn Falseclass 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_datarequest_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_noorder_id = request.GET['out_trade_no']#查询订单表状态,如果还是待付款,采取B方案 - 主动查询支付宝 订单真实交易状态#主动查询result = self.get_trade_result(order_id)if result:return HttpResponse('--支付成功--主动查询')else:return HttpResponse('--支付异常--主动查询')
    

Django博客项目实战相关推荐

  1. php博客视频教程,ThinkPHP5 博客项目实战视频教程

    ThinkPHP是为了简化企业级应用开发和敏捷WEB应用开发而诞生的.最早诞生于2006年初,2007年元旦正式更名为ThinkPHP,并且遵循Apache2开源协议发布.ThinkPHP从诞生以来一 ...

  2. Spring Boot + vue-element 开发个人博客项目实战教程(四、数据库搭建和配置)

    前言 java项目已经创建好了,接下来我们要准备数据库了,数据库是干嘛的相信大家都知道了,我在这就不说了,我们需要做的就是在电脑上安装mysql数据库. 附菜鸟教程的MySQL基础学习教程:https ...

  3. Spring Boot + vue-element 开发个人博客项目实战教程(二十五、项目完善及扩展(前端部分))

    ⭐ 作者简介:码上言 ⭐ 代表教程:Spring Boot + vue-element 开发个人博客项目实战教程 ⭐专栏内容:零基础学Java.个人博客系统 ⭐我的文档网站:http://xyhwh- ...

  4. Spring Boot + vue-element 开发个人博客项目实战教程(十三、文章标签功能实现)

    ⭐ 作者简介:码上言 ⭐ 代表教程:Spring Boot + vue-element 开发个人博客项目实战教程 ⭐专栏内容:零基础学Java.个人博客系统 项目部署视频 https://www.bi ...

  5. 最新后盾网Laravel框架重入门到实战 Laravel博客项目实战 陈华主讲 包含课件源码

    老师介绍 陈华,PHP明星讲师,大学生演讲网创始人,2010年开始开发整站项目,精通Laravel,Yii框架. 简 介 本套课程是一套以项目实战为主的Laravel5.2实战开发教程,是真正意义上的 ...

  6. Spring Boot + vue-element 开发个人博客项目实战教程(一、项目介绍和规划)

    ⭐ 作者简介:码上言 ⭐ 代表教程:Spring Boot + vue-element 开发个人博客项目实战教程 ⭐专栏内容:零基础学Java.个人博客系统 ⭐我的文档网站:http://xyhwh- ...

  7. django博客项目8:文章详情页

    首页展示的是所有文章的列表,当用户看到感兴趣的文章时,他点击文章的标题或者继续阅读的按钮,应该跳转到文章的详情页面来阅读文章的详细内容.现在让我们来开发博客的详情页面,有了前面的基础,开发流程都是一样 ...

  8. Node.js Express博客项目实战 之 前台页面数据的显示

    前台页面数据通过连接数据库信息将数数据显示在页面上: 最终实现的效果: 前台的路由: 1 // 导入express 2 3 let express = require("express&qu ...

  9. Go后端博客项目实战_持续更新ing

    前言 因为是仿写他人的优秀博客后端,已经把博客代码拉下,进行分析. 但是在分析的过程中自己理解的很困难. 所以想出了一种解决方法: 将原项目代码拉下后,大概研究一下该项目实现的基本功能有哪些?自己能不 ...

  10. django博客项目-文章详情页功能

    文章详情页左侧边栏数据复用 文章详情页和个人站点的左侧边栏的内容格式都是一样的,从个人站点路由和文章详情路由进入到网页,侧边栏显示的内容是一模一样,解决方案: 方案一: 写一个home_site.ht ...

最新文章

  1. [YTU]_2617(B C++时间类的运算符重载)
  2. Git 仓库基础操作
  3. 安卓学习笔记42:基于HTTP网络编程
  4. Integer与int的区别
  5. .Net/C# 应用程序直接读取本地 Cookies 文件(WinXP SP2 调用 API: InternetGetCookie 无果)...
  6. [转]C#操作Excel开发报表系列整理
  7. python unittest教程_python unittest 基本介绍
  8. 将GitHub源代码打包成jar包
  9. 伽罗华域(有限域)及其运算规则(包含大量例子)
  10. STM32CUBE——使用DWT提供毫秒延迟
  11. oracle 删除数据违反约束条件,ORA-02292: 违反完整约束条件 处理
  12. C/C++中各种类型char、int、long、double等数据范围
  13. 微信小程序API 文件·文件管理器
  14. 如何在os x或ubuntu下安装最新的ruby
  15. 【第1170期】如何看待员工跳槽
  16. 跳槽首选,平均薪资2w+!现在入门快人一步
  17. js打印pdf 使用Adobe reader 打印pdf
  18. JAVA计算机毕业设计甜趣网上蛋糕店订购系统(附源码、数据库)
  19. Java张孝祥视频 学习笔记 代理
  20. TOFEL Speaking 托福口语 —— 模板

热门文章

  1. hive怎么发音_Hadoop Hive概念学习系列之hive里的HiveQL——查询语言(十五)
  2. 机器学习-Numpy的学习
  3. 逸致金品:如何从零开始学习板绘?
  4. 云计算是商业模式创新而非技术创新
  5. 不需要手机号,怎样注册邮箱账号
  6. 2020-02-13
  7. 认知水平高下定义及提高认知水平的方法
  8. 世界各个国家或地区国际域名缩写(Countries and Regions)
  9. MacOS - MacBook - 推荐工具收集
  10. Android菜单不显示图标,Android系统手机让通知栏不显示某个软件的图标 | 坐倚北风...