# 华为云服务器+Nginx+Python3.7+Django2.2+支付宝支付接入部署
> 本次分享内容共分四个主要部署步骤
> 1.Django框架项目搭建部署
> 2.本地内网穿透测试
> 3.阿里支付宝支付接口部署
> 4.华为云服务器部署(弹性云服务器ECS)
## 一,搭建django项目基本结构
### 1.创建当前项目的虚拟环境
` python3 -m venv venv `
### 2.安装依赖环境
> ` pip install -r requirement.txt`
```
Django==2.2.6
Pillow==6.2.0
pkg-resources==0.0.0
pycrypto==2.6.1
pycryptodomex==3.7.2
python-alipay-sdk==1.10.1
pytz==2019.3
sqlparse==0.3.0
```
### 3.完成项目基本结构开发(略...)
> 按照基本模型和路由参考,完成以下基本项目功能:
> 1.商品列表页:需要完成数据的查询及模板中的数据展示
> 2.商品下单: 在商品列表页可以对商品进行下单购买,及对应的订单数据入库操作
> 3.订单列表: 可以查看到当前所有订单及订单的支付状态
> 4.完成发起支付请求,支付回调地址的视图函数定义(代码可以暂时不写)
模型 models.py
```python
from django.db import models
from django.utils.html import format_html
# Create your models here.
# 书籍模型
class Books(models.Model):
# 书名
name = models.CharField(max_length=30)
# 价格
price = models.FloatField()
# 数量
num = models.IntegerField(default=5)
# 封面
img_url = models.ImageField(upload_to="./static/uploads/",null=True)
def loadimg(self):
return format_html('<img src="%s" height="64" width="64" />' %(self.img_url,))
class Order(models.Model):
# 订单号
ordercode = models.IntegerField()
# 下单用户id
user = models.CharField(max_length=5,default='测试用户')
# 购买产品id
bookid = models.IntegerField()
# 产品名称
bookname = models.CharField(max_length=50)
# 应付金额
monery = models.FloatField()
# 支付方式 0 支付宝
paytype = models.IntegerField(default=0)
# 支付状态 0未支付 1 已支付
paystatus = models.IntegerField(default=0)
# 订单创建时间
ordertime = models.DateTimeField(auto_now_add=True)
# 订单支付时间
paytime = models.DateTimeField(null=True)
```
路由及对应视图函数 urls.py
```python
# 商品列表
path('',views.index),
# 创建订单
path('order/create', views.create_order,name="createOrder"),
# 发起支付请求
path('order/pay', views.order_pay_request,name="orderpay"),
# 支付宝回调地址
path('order/pay_result', views.order_pay_result,name="order_pay_result"),
# 订单列表,支付成功后的跳转页面
path('order/list', views.orderlist,name="orderlist"),
# 订单删除
path('order/delete', views.orderdel,name="orderdel"),
```
## 二,使用ngrok|花生壳内网穿透
> 推荐使用花生壳进行内网穿透测试
> http://service.oray.com/question/1664.html
## 三,支付宝接入
#### 1.登陆支付宝开放平台创建支付宝沙箱环境
> 支付宝开放平台 https://openhome.alipay.com/platform/appDaily.htm?tab=info
> 支付文档 https://docs.open.alipay.com/200/105311
#### 2.创建密钥
> 1.生成应用公钥和秘钥
> 2.把应用公钥赋值并配置到当前的沙箱环境中
> 3.配置完公钥后,沙箱环境配置会给一个支付宝公钥,复制并保存
> 4.在项目根目录中创建keys文件目录,存储应用私钥(rsa_private_key.txt)和支付宝公钥(rsa_public_key.txt)
ubuntu生成密钥和公钥
```shell
#打开终端输入 openssl
# 输入以下命令创建密钥
genrsa -out rsa_private_key.txt 2048
# 输入以下命令创建公钥
rsa -in rsa_private_key.txt -pubout -out rsa_public_key.txt
#输入 exit 推出 openssl
# ls 查看当前目录下创建的密钥和公钥
rsa_private_key.txt rsa_public_key.txt
```
windows10 可以安装支付宝开放平台助手,创建密钥
> https://docs.open.alipay.com/291/105971
注意:在项目中配置 keys应用 私钥和支付宝公钥 放进来
```python
1,在项目中 创建 keys 目录 里面放入 秘钥文件
2,创建 rsa_private_key.txt 放入秘钥,加开始和结束的标记
-----BEGIN RSA PRIVATE KEY-----
.....
-----END RSA PRIVATE KEY-----
3,创建 rsa_public_key.txt 放入秘钥,加开始和结束的标记
-----BEGIN PUBLIC KEY-----
....
-----END PUBLIC KEY-----
```
#### 3. 项目中支付宝接口的配置 settings.py
1.参考以下配置在项目中进行支付宝相关配置
```python
# 支付宝相关配置
# APPID
# 沙箱APPID,生产环境须更改为应用APPID。
ALIPAY_APPID = "0000000000011111100"
# 网关
# 沙箱网关,生产环境须更改为正式网关。
ALIPAY_URL = "https://openapi.alipaydev.com/gateway.do"
# 正式网关,开发环境勿使用。
# ALIPAY_URL = "https://openapi.alipay.com/gateway.do"
# 回调通知地址
ALIPAY_NOTIFY_URL = "http://mv23102380.imwork.net/order/pay_result"
# 支付后的跳转地址
ALIPAY_RETURN_URL = 'http://mv23102380.imwork.net/order/pay_result'
# 应用私钥
APP_PRIVATE_KEY_PATH = os.path.join(BASE_DIR, 'keys/rsa_private_key.txt')
# 支付宝公钥
ALIPAY_PUBLIC_KEY_PATH = os.path.join(BASE_DIR, 'keys/rsa_public_key.txt')
```
#### 4.发起支付请求
1. 完成支付请求前基本开发
```python
# 首页
def index(request):
data = Books.objects.all()
return render(request,'index.html',{'data':data})
# 创建订单,发起支付请求
def create_order(request):
# 接受表单数据
id = request.POST.get('id')
# 获取对象
obj = Books.objects.get(id=id)
# 检测库存
if obj.num <= 0:
return JsonResponse({'code':1,'msg':'当前商品已经售空'})
# 创建订单
# 订单号,购买产品id,应付金额
data = {
'ordercode':int(time.time())+random.randint(10000,99999),
'bookid':obj.id,
'bookname':obj.name,
'monery':obj.price
}
orderobj = Order(**data)
orderobj.save()
print(f'订单创建成功,\r\n订单信息:{data}')
# return HttpResponse('创建订单,发起支付请求')
return order_pay_request(orderobj)
# 支付成功后的跳转页面
def orderlist(request):
# 获取所有的订单数据
data = Order.objects.all()
for i in data:
i.img = Books.objects.get(id=i.bookid).img_url
return render(request,'orderlist.html',{'data':data})
# 订单删除
def orderdel(request):
oid = request.GET.get('oid')
obj = Order.objects.get(id=oid)
obj.delete()
return HttpResponseRedirect(reverse('orderlist'))
```
2. 导入支付宝支付接口类
> 在项目根目录创建utils包文件夹,创建pay.py模块写入支付接口类
> web\utils\pay.py
```python
from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json
class AliPay(object):
"""
支付宝支付接口(PC端支付接口)
"""
def __init__(self, appid, app_notify_url, app_private_key_path,
alipay_public_key_path, return_url, debug=False):
self.appid = appid
self.app_notify_url = app_notify_url
self.app_private_key_path = app_private_key_path
self.app_private_key = None
self.return_url = return_url
with open(self.app_private_key_path) as fp:
self.app_private_key = RSA.importKey(fp.read())
self.alipay_public_key_path = alipay_public_key_path
with open(self.alipay_public_key_path) as fp:
self.alipay_public_key = RSA.importKey(fp.read())
if debug is True:
self.__gateway = "https://openapi.alipaydev.com/gateway.do"
else:
self.__gateway = "https://openapi.alipay.com/gateway.do"
def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
biz_content = {
"subject": subject,
"out_trade_no": out_trade_no,
"total_amount": total_amount,
"product_code": "FAST_INSTANT_TRADE_PAY",
# "qr_pay_mode":4
}
biz_content.update(kwargs)
data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
return self.sign_data(data)
def build_body(self, method, biz_content, return_url=None):
data = {
"app_id": self.appid,
"method": method,
"charset": "utf-8",
"sign_type": "RSA2",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"biz_content": biz_content
}
if return_url is not None:
data["notify_url"] = self.app_notify_url
data["return_url"] = self.return_url
return data
def sign_data(self, data):
data.pop("sign", None)
# 排序后的字符串
unsigned_items = self.ordered_data(data)
unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
sign = self.sign(unsigned_string.encode("utf-8"))
# ordered_items = self.ordered_data(data)
quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)
# 获得最终的订单信息字符串
signed_string = quoted_string + "&sign=" + quote_plus(sign)
return signed_string
def ordered_data(self, data):
complex_keys = []
for key, value in data.items():
if isinstance(value, dict):
complex_keys.append(key)
# 将字典类型的数据dump出来
for key in complex_keys:
data[key] = json.dumps(data[key], separators=(',', ':'))
return sorted([(k, v) for k, v in data.items()])
def sign(self, unsigned_string):
# 开始计算签名
key = self.app_private_key
signer = PKCS1_v1_5.new(key)
signature = signer.sign(SHA256.new(unsigned_string))
# base64 编码,转换为unicode表示并移除回车
sign = encodebytes(signature).decode("utf8").replace("\n", "")
return sign
def _verify(self, raw_content, signature):
# 开始计算签名
key = self.alipay_public_key
signer = PKCS1_v1_5.new(key)
digest = SHA256.new()
digest.update(raw_content.encode("utf8"))
if signer.verify(digest, decodebytes(signature.encode("utf8"))):
return True
return False
def verify(self, data, signature):
if "sign_type" in data:
sign_type = data.pop("sign_type")
# 排序后的字符串
unsigned_items = self.ordered_data(data)
message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
return self._verify(message, signature)
```
3. 支付请求及回调函数封装
```python
# 发起支付请求
def order_pay_request(orderobj):
# 获取支付对象
alipay = Get_AliPay_Object()
# 生成支付的url
query_params = alipay.direct_pay(
subject=orderobj.bookname, # 商品简单描述
out_trade_no = orderobj.ordercode,# 用户购买的商品订单号
total_amount = orderobj.monery, # 交易金额(单位: 元 保留俩位小数)
)
# 支付宝网关地址(沙箱应用)
pay_url = settings.ALIPAY_URL+"?{0}".format(query_params)
print('正在发起支付请求...')
# 页面重定向到支付页面
return HttpResponseRedirect(pay_url)
# 支付宝回调地址
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def order_pay_result(request):
# 获取对象
alipay = Get_AliPay_Object()
if request.method == "POST":
# 检测是否支付成功
# 去请求体中获取所有返回的数据:状态/订单号
from urllib.parse import parse_qs
# name&age=123....
body_str = request.body.decode('utf-8')
post_data = parse_qs(body_str)
post_dict = {}
for k, v in post_data.items():
post_dict[k] = v[0]
sign = post_dict.pop('sign', None)
status = alipay.verify(post_dict, sign)
print('------------------开始------------------')
print('POST验证', status)
print(post_dict)
out_trade_no = post_dict['out_trade_no']
# 修改订单状态
ass = {'paystatus':1,'paytime':post_dict['gmt_payment']}
print(ass)
Order.objects.filter(ordercode=out_trade_no).update(**ass)
print('------------------结束------------------')
# 修改订单状态:获取订单号
return HttpResponse('success')
else:
params = request.GET.dict()
sign = params.pop('sign', None)
status = alipay.verify(params, sign)
print('==================开始==================')
print('GET验证', status)
print('==================结束==================')
return HttpResponse('<script>alert("支付成功");location.href="/order/list"</script>')
# 支付宝对象创建方法
from web import settings
from utils.pay import AliPay
# AliPay 对象实例化
def Get_AliPay_Object():
alipay = AliPay(
appid=settings.ALIPAY_APPID,# APPID (沙箱应用)
app_notify_url=settings.ALIPAY_NOTIFY_URL, # 回调通知地址
return_url=settings.ALIPAY_RETURN_URL,# 支付完成后的跳转地址
app_private_key_path=settings.APP_PRIVATE_KEY_PATH, # 应用私钥
alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH, # 支付宝公钥
debug=True, # 默认False,
)
return alipay
```
## 四.上线华为云服务器部署(弹性云服务器ECS)
环境配置:
ubuntu 18.04
Python 3.6.8(python3.7亦可)
nginx version: nginx/1.14.0 (Ubuntu)
#### 1. 购买华为云服务器
> 文档 https://support.huaweicloud.com/ecs/index.html
#### 2. 上传到华为云服务器,安装依赖环境,启动项目测试
#### 3. 搭建uwsgi启动项目测试
1.安装uwsgi
sudo pip3 install uwsgi --upgrade
2.安装完成后使用命令测试
先进入项目目录,启动命令
uwsgi --http :80 --chdir /home/alipay/web --module web.wsgi --home /home/alipay/venv/bin
# --home 指定virtualenv 路径,如果没有可以去掉。web.wsgi 指的是 web/wsgi.py 文件
3.访问测试,启动成功后
127.0.0.1:8080
#### 4. 配置uwsgi文件启动项目
第一步:创建一个uwsgi.ini文件
第二步:在django项目同级目录创建script目录,用于存放配置脚本等等
/home/alipay/web/
script/ web/ db.sqlite3 manage.py uwsgi.ini
第三步:编辑uwsgi.ini文件内容如下: 目录参考个人目录进行修改
```shell
# uwsig使用配置文件启动
[uwsgi]
# 项目目录
chdir=/home/alipay/web/
# 指定项目的application
module=web.wsgi:application
# 指定sock的文件路径
socket=/home/alipay/web/script/uwsgi.sock
# 进程个数
workers=5
pidfile=/home/alipay/web/script/uwsgi.pid
# 指定IP端口
http=0.0.0.0:8000
# 指定静态文件
#static-map=/static=/home/alipay/web/static/
# 启动uwsgi的用户名和用户组
uid=www-data
gid=www-data
# 启用主进程
master=true
# 自动移除unix Socket和pid文件当服务停止的时候
vacuum=true
# 序列化接受的内容,如果可能的话
thunder-lock=true
# 启用线程
enable-threads=true
# 设置自中断时间
harakiri=30
# 设置缓冲
post-buffering=4096
# 设置日志目录
daemonize=/home/alipay/web/script/uwsgi.log
# 权限
chmod-socket = 666
chown-socket = www-data
```
#### 5.执行命令,启动项目测试
uwsgi --ini uwsgi.ini
在浏览器访问127.0.0.1:8000
#### 6,安装nginx
1.安装nginx
sudo apt-get install python-dev nginx
2.安装完成后,可以使用通过浏览器访问公网IP测试
3.创建项目的配置文件,或者直接修改原nginx配置文件都可以
vim /etc/nginx/sites-available/webtest.conf
```nginx文件内容根据个人情况自行调整
server {
listen 80;
server_name localtion;
charset utf-8;
client_max_body_size 75M;
location / {
uwsgi_pass unix:///home/alipay/web/script/uwsgi.sock;
include /etc/nginx/uwsgi_params;
}
#location /media {
# alias /path/to/project/media;
#}
location /static {
alias /home/alipay/web/static/;
}
}
```
4.创建完配置文件后创建软连接
sudo ln -s /etc/nginx/sites-available/webtest.conf /etc/nginx/sites-enabled/webtest.conf
5.启动nginx,重新加载配置文件
nginx -s reload
#### 注意:
1.给当前项目设置访问权限。www-data
2.注意在nginx的配置中uwsgi_pass这一项
uwsgi_pass unix:///home/yc/web/script/uwsgi.sock;
它需要找到你在启动uwsgi后的sock文件,并且要注意权限
3.在使用uwsgi --ini uwsgi.ini启动后会创建 .sock文件
那么在后面更新代码时不需要把 script目录下的文件都删除
只需要重启即可
uwsgi --reload xxx.pid