项目名称:久至博客
开发模式:前后端分离模式
主体功能:
1)用户功能
2)文章功能
3)留言功能
4)支付功能
前后端分离完整请求过程
各司其职:
前端:视觉层面,兼容性,前端性能优化
后端:并发,可用性,性能
解耦,前端和后端均易于扩展
后端灵活搭配各类前端 - 如安卓等
提供用户体验
前端+后端可完全并行开发,加快开发效率
问题 | 解决方案 |
---|---|
解决HTTP无状态? | 采用token(令牌) |
前端为JS,如何解决跨域问题? | 采用CORS |
如何解决CSRF问题 | 采用token |
是否会影响Search Engine Optimization(SEO)效果 | 会,前后端分离后,往往页面不存在静态文字【例如新闻的详细内容】 |
逻辑是由前端还是后端完成? | 底线原则:数据校验需要前后端都做 |
前端工作压力大 | 团队协作 |
动静分离和前后端分离的区别 | 动静分离是指 css/js/img静态资源 和服务器 拆开部署,典型方案-静态资源交由CDN厂商处理【蓝汛 网宿 阿里云 腾讯云】 |
判断前后端分离的核心标准:谁生成显示页面
跨域资源共享(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;如果有需要获取其他头,需在此指定 | 可选 |
不满足简单请求的条件则为预检请求
请求头 | 作用 | 备注 |
---|---|---|
Origin | 表明此请求来自哪个域 | 必选 |
Access-Control-Request-Method | 此次请求使用方法 | 必选 |
Access-Control-Request-Headers | 此次请求使用的头 | 必选 |
响应头 | 作用 | 备注 |
---|---|---|
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天 | 可选 |
作用 | 请求头 | 备注 |
---|---|---|
表明此请求来自哪个域 | Origin | 必选 |
响应头 | 作用 | 备注 |
---|---|---|
Access-Control-Allow-Origin | 服务器接受的域 |
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
全称:Representational State Transfer
1.资源(Resources)
网络上的一个实体,或者说是网络上的一个具体信息,并且么每个资源都有一个独一无二的URL与之对应;获取资源直接访问URL即可
2.表现层(Representation)
资源的表现形式;如HTML、xml、JPG、json等
3.状态转化(State Transfer)
访问一个URL即发生了一次客户端和服务端的交互,此次交互将会涉及到数据和状态的变化;客户端需要通过某些方式触发具体的变化 - HTTP method,如GET、POST、PUT、PATCH、DELETE 等
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())
]
import base64
b64encode # 转为base64规则的串
b64decode # 解密
urlsafe-b64encode # base64加密,但会将 '+' 替换成 '-',将 '/' 替换成 '_'
urlsafe-b64decode # 解密
import max
h = hmax.new(key,str,digestmod ='SHA256')
h.digest() # 获取结果
""" 生成hmax对象
key 为加密秘钥(可自定义),bytes类型,
str 为需加密的串,bytes类型
digestmod 为hmac的算法,指定为 SHA256
"""
Cookies
将会话数据存储在浏览器上,浏览器每次给站点发请求自动将Cookies数据提交至服务器
session
将会话数据存储在服务器上(Django存储在数据库中),需要借助浏览器的Cookies
1.header
格式为字典,需要转成json串并用base64转码
{'alg':'HS256','typ':'JWT'}
""" alg 代表要使用的 算法
typ 表明该token的类别 - 此处必须为 大写的 JWT
"""
2.payload
{ 'exp':xxx , # Expiration Tiem 此token的过期时间的时间戳
'iss':xxx , # (Issuer) Claim 指明此token的签发者
'iat':xxx , # (Issued At) Claim 指明此创建时间的时间戳
'aud':xxx , # (Audience) Claim 指明此token签发面向群体(IOS,Android等)
}
{'username':'testuser'}
3.signature
根据header中的alg确定具体算法,以下用HS256为例
HS256(自定义的key,base64后的header + ‘.’ + base64后的payload)
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
# 参考接入文档,形成的发送短信类
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是一个简单、灵活且可靠的,处理大量消息的分布式系统
他是一个专注于实时处理的任务队列,同时也支持任务调度
中文官网:http://docs/jinkan.org/docs/celery/
pip3 freeze|grep -i 'celery' # 检验
sudo pip3 install Celery # 安装
Broker - 消息中间件,消息传输的中间件,生产者将消息发至broker【RQ,redis】
worker - 工作者 -消费/执行broker中的消息/任务的进程
backend - 结果存储- 用于存储消息/任务结果,如果需要跟踪和查询任务状态,则需要添加相关配置
如何判断是否需要业务是否需要使用Celery(异步)?
# 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日志即可
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, &符号:代表将命令在后台执行
"""
缓存 - 把高频读取的数据,放置到更快的存储介质里
优点:灵活,存储成本最优,删除成本低
缺点:代码实现成本较高
案例:模型类中,定义 classmethod 方法
class Topic
@classmethod
def get_topic_list(cls,):
if cache:
return cache
data = cls.objects.filter(....)
# cache in
return data
优缺点,综合了方案一和方案二,满足需求
# 装饰器中加参数,外层再套一层函数
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钥匙
# 终端生成
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
在弹出的对话框中,选择 公钥 模式,并将 本地生成的 app_public_key.pem中的 内容复制 到框中,并保存复制 支付宝公钥 为 alipay_public_key.pem
注意:生成公钥如下,只复制 -----BEGIN PUBLIC KEY ------ 和 -----END PUBLIC KEY-----之间的内容,保存支付宝公钥同理
# 安装 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 输出此结果,则表示安装成功
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('--支付异常--主动查询')