parent
a20d3c7dff
commit
f9de02f139
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@ |
||||
# Generated by Django 3.0.3 on 2020-03-23 12:20 |
||||
|
||||
from django.conf import settings |
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('api', '0012_userinfo_head_img'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterField( |
||||
model_name='appreleaseinfo', |
||||
name='is_master', |
||||
field=models.BooleanField(default=True, verbose_name='是否master版本'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='userinfo', |
||||
name='head_img', |
||||
field=models.CharField(default='/files/imgs/head_img.jpeg', max_length=256, verbose_name='个人头像'), |
||||
), |
||||
migrations.CreateModel( |
||||
name='AppStorage', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('name', models.CharField(blank=True, max_length=64, null=True, verbose_name='存储名字')), |
||||
('storage_type', models.SmallIntegerField(choices=[(0, '本地存储'), (1, '七牛云存储'), (2, '阿里云存储'), (3, '默认存储')], default=3, verbose_name='存储类型')), |
||||
('access_key', models.CharField(blank=True, max_length=128, null=True, verbose_name='存储访问key')), |
||||
('secret_key', models.CharField(blank=True, max_length=128, null=True, verbose_name='存储访问secret')), |
||||
('bucket_name', models.CharField(blank=True, max_length=128, null=True, verbose_name='存储空间bucket_name')), |
||||
('additionalparameters', models.TextField(blank=True, help_text='阿里云:{"sts_role_arn":"arn信息","endpoint":""} 七牛云:{"domain_name":""}', max_length=256, null=True, verbose_name='额外参数')), |
||||
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
||||
('description', models.TextField(blank=True, default=None, null=True, verbose_name='备注')), |
||||
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户ID')), |
||||
], |
||||
options={ |
||||
'verbose_name': '存储配置', |
||||
'verbose_name_plural': '存储配置', |
||||
}, |
||||
), |
||||
] |
@ -0,0 +1,19 @@ |
||||
# Generated by Django 3.0.3 on 2020-03-23 12:35 |
||||
|
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('api', '0013_auto_20200323_2020'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='userinfo', |
||||
name='storage', |
||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='app_storage', to='api.AppStorage', verbose_name='存储'), |
||||
), |
||||
] |
@ -1,192 +0,0 @@ |
||||
from django.test import TestCase |
||||
|
||||
# # Create your tests here. |
||||
# import base64 |
||||
# request_token='ZWZhN2MwNGMwMzg2MWVmMzA5YjUxNDlkMGU0ZWIwNDRiNTlmZWJjYzpuaW5ldmVuQHFxLmNvbQ%3D%3D==' |
||||
# print(base64.b64decode(request_token)) |
||||
|
||||
|
||||
# import xml.etree.ElementTree as ET |
||||
# tree = ET.parse("xmlfir.xml") |
||||
# root = tree.getroot() |
||||
# print(root) |
||||
# print(root.tag) #data |
||||
# for child in root: |
||||
# print('========>',child.tag,child) # |
||||
# #========> country {'name': 'Liechtenstein'} Liechtenstein |
||||
# for i in child: |
||||
# print(i.tag,i.attrib,i.text) |
||||
|
||||
|
||||
import xml.etree.ElementTree as ET |
||||
|
||||
ios_plist = ET.Element("plist", attrib={"version": "1.0"}) |
||||
dict1 = ET.SubElement(ios_plist, "dict") |
||||
key1 = ET.SubElement(dict1, "key") |
||||
key1.text = 'items' |
||||
array1 = ET.SubElement(dict1, "array") |
||||
dict2 = ET.SubElement(array1, "dict") |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
et = ET.ElementTree(ios_plist) # 生成文档对象 |
||||
# et.write("test.xml", encoding="utf-8", xml_declaration=True) |
||||
|
||||
ET.dump(ios_plist) # 打印生成的格式 |
||||
|
||||
import json |
||||
|
||||
import xmltodict |
||||
|
||||
xjson = json.dumps(xmltodict.parse(""" |
||||
|
||||
<plist version="1.0"><dict> |
||||
<key>items</key> |
||||
<array> |
||||
<dict> |
||||
<key>assets</key> |
||||
<array> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>software-package</string> |
||||
<key>url</key> |
||||
<string><![CDATA[https://ali-fir-pro-binary.jappstore.com/52e972d2749a404028e2ab7edb04d7a7f365184d?auth_key=1583720494-0-0-3dc9d1bf4f093c53f1193614ee5f6d3e]]></string> |
||||
</dict> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>display-image</string> |
||||
<key>needs-shine</key> |
||||
<integer>0</integer> |
||||
<key>url</key> |
||||
<string><![CDATA[https://ali-fir-pro-icon.fir.im/6be40db9e2bb729db1ce0db93b8ebdba50ea8b4d?auth_key=1583720494-0-0-0212635dc7c21a3c02cd2422d0213e99]]></string> |
||||
</dict> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>full-size-image</string> |
||||
<key>needs-shine</key> |
||||
<true/> |
||||
<key>url</key> |
||||
<string><![CDATA[https://ali-fir-pro-icon.fir.im/6be40db9e2bb729db1ce0db93b8ebdba50ea8b4d?auth_key=1583720494-0-0-0212635dc7c21a3c02cd2422d0213e99]]></string> |
||||
</dict> |
||||
</array> |
||||
<key>metadata</key> |
||||
<dict> |
||||
<key>bundle-identifier</key> |
||||
<string>com.fengxin.im.test</string> |
||||
<key>bundle-version</key> |
||||
<string><![CDATA[1.1.7]]></string> |
||||
<key>kind</key> |
||||
<string>software</string> |
||||
<key>title</key> |
||||
<string><![CDATA[亲友圈发发]]></string> |
||||
</dict> |
||||
</dict> |
||||
</array> |
||||
</dict> |
||||
</plist> |
||||
"""), indent=4) |
||||
|
||||
# print(xjson) |
||||
# print(xjson["plist"]["dict"]["array"]["dict"]) |
||||
# Fdata = xjson["plist"]["dict"]["array"]["dict"] |
||||
# odata = Fdata["array"]["dict"] |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bin_url="bin_url" |
||||
img_url="img_url" |
||||
bundle_id="bundle_id" |
||||
bundle_version="bundle_version" |
||||
name="name" |
||||
|
||||
ios_plist_tem=""" |
||||
{ |
||||
"plist": { |
||||
"@version": "1.0", |
||||
"dict": { |
||||
"key": "items", |
||||
"array": { |
||||
"dict": { |
||||
"key": [ |
||||
"assets", |
||||
"metadata" |
||||
], |
||||
"array": { |
||||
"dict": [ |
||||
{ |
||||
"key": [ |
||||
"kind", |
||||
"url" |
||||
], |
||||
"string": [ |
||||
"software-package", |
||||
"%s" |
||||
] |
||||
}, |
||||
{ |
||||
"key": [ |
||||
"kind", |
||||
"needs-shine", |
||||
"url" |
||||
], |
||||
"string": [ |
||||
"display-image", |
||||
"%s" |
||||
], |
||||
"integer": "0" |
||||
}, |
||||
{ |
||||
"key": [ |
||||
"kind", |
||||
"needs-shine", |
||||
"url" |
||||
], |
||||
"string": [ |
||||
"full-size-image", |
||||
"%s" |
||||
], |
||||
"true": null |
||||
} |
||||
] |
||||
}, |
||||
"dict": { |
||||
"key": [ |
||||
"bundle-identifier", |
||||
"bundle-version", |
||||
"kind", |
||||
"title" |
||||
], |
||||
"string": [ |
||||
"%s", |
||||
"%s", |
||||
"software", |
||||
"%s" |
||||
] |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
""" %(bin_url,img_url,img_url,bundle_id,bundle_version,name) |
||||
|
||||
|
||||
# print(xmltodict.unparse(json.loads(ios_plist_tem), indent=4)) |
||||
|
||||
|
||||
tokenlist=[ |
||||
{'a':{ |
||||
'data':['1','fsdf'], |
||||
'time':65225 |
||||
}} |
||||
] |
||||
|
||||
for token in tokenlist: |
||||
print(token) |
||||
for k,v in token.items(): |
||||
print(k,v) |
@ -1,10 +0,0 @@ |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class CommonException(Exception): |
||||
def __init__(self,code,msg): |
||||
self.error=msg |
||||
self.code=code |
@ -1,23 +0,0 @@ |
||||
|
||||
from api import models |
||||
from rest_framework.filters import BaseFilterBackend |
||||
|
||||
class CourseFilter(BaseFilterBackend): |
||||
""" |
||||
课程展示 过滤器 |
||||
""" |
||||
|
||||
def filter_queryset(self,request, queryset, view): |
||||
extra = {} |
||||
print("request.query_params",request.query_params) |
||||
category_id = str(request.query_params.get("category_id")) |
||||
|
||||
# 如果分类ID不是数字或分类ID传输的为0 |
||||
print("category_id",category_id) |
||||
if not category_id.isdigit() or category_id == "0": |
||||
extra = extra |
||||
else: |
||||
extra.update({"course_category_id":category_id}) |
||||
print("extra",extra) |
||||
print("queryset.filter(**extra)",queryset.filter(**extra)) |
||||
return queryset.filter(**extra) |
@ -0,0 +1,113 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 3月 |
||||
# author: liuyu |
||||
# date: 2020/3/22 |
||||
|
||||
#pip install aliyun-python-sdk-sts oss2 |
||||
|
||||
import json |
||||
import os |
||||
|
||||
from aliyunsdkcore import client |
||||
from aliyunsdksts.request.v20150401 import AssumeRoleRequest |
||||
|
||||
import oss2 |
||||
|
||||
|
||||
# 以下代码展示了STS的用法,包括角色扮演获取临时用户的密钥、使用临时用户的密钥访问OSS。 |
||||
|
||||
# STS入门教程请参看 https://yq.aliyun.com/articles/57895 |
||||
# STS的官方文档请参看 https://help.aliyun.com/document_detail/28627.html |
||||
|
||||
# 首先初始化AccessKeyId、AccessKeySecret、Endpoint等信息。 |
||||
# 通过环境变量获取,或者把诸如“<你的AccessKeyId>”替换成真实的AccessKeyId等。 |
||||
# 注意:AccessKeyId、AccessKeySecret为子用户的密钥。 |
||||
# 子用户需要有 调用STS服务AssumeRole接口的权限 |
||||
# 创建ram用户,授权 管理对象存储服务(OSS)权限 |
||||
# RoleArn可以在控制台的“访问控制 > RAM角色管理 > 创建的ram用户 > 基本信息 > Arn”上查看。 |
||||
# |
||||
# 以杭州区域为例,Endpoint可以是: |
||||
# http://oss-cn-hangzhou.aliyuncs.com |
||||
# https://oss-cn-hangzhou.aliyuncs.com |
||||
# 分别以HTTP、HTTPS协议访问。 |
||||
|
||||
class StsToken(object): |
||||
"""AssumeRole返回的临时用户密钥 |
||||
:param str access_key_id: 临时用户的access key id |
||||
:param str access_key_secret: 临时用户的access key secret |
||||
:param int expiration: 过期时间,UNIX时间,自1970年1月1日UTC零点的秒数 |
||||
:param str security_token: 临时用户Token |
||||
:param str request_id: 请求ID |
||||
""" |
||||
def __init__(self): |
||||
self.access_key_id = '' |
||||
self.access_key_secret = '' |
||||
self.expiration = 3600 |
||||
self.security_token = '' |
||||
self.request_id = '' |
||||
self.bucket='' |
||||
self.endpoint='' |
||||
|
||||
class AliYunOss(object): |
||||
|
||||
def __init__(self,access_key,secret_key,bucket_name,endpoint,sts_role_arn): |
||||
self.access_key_id = access_key or os.getenv('OSS_TEST_STS_ID', 'LTAI4FeTyvz74CHKCbYuTDGc') |
||||
self.access_key_secret = secret_key or os.getenv('OSS_TEST_STS_KEY', 'uineAEE7tp1d1w4Mv0yLqxvV0IVNTy') |
||||
self.bucket_name = bucket_name or os.getenv('OSS_TEST_BUCKET', 'fir-storage') |
||||
self.endpoint = endpoint or os.getenv('OSS_TEST_ENDPOINT', 'oss-cn-beijing.aliyuncs.com') |
||||
self.sts_role_arn = sts_role_arn or os.getenv('OSS_TEST_STS_ARN', 'acs:ram::1622120977387033:role/appstorage') |
||||
self.region_id = '-'.join( self.endpoint.split('.')[0].split("-")[1:3]) |
||||
self.token = StsToken() |
||||
self.get_auth_bucket('init', 1800) |
||||
|
||||
|
||||
def fetch_sts_token(self,name,expires): |
||||
"""子用户角色扮演获取临时用户的密钥 |
||||
:param access_key_id: 子用户的 access key id |
||||
:param access_key_secret: 子用户的 access key secret |
||||
:param role_arn: STS角色的Arn |
||||
:return StsToken: 临时用户密钥 |
||||
""" |
||||
clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region_id) |
||||
req = AssumeRoleRequest.AssumeRoleRequest() |
||||
|
||||
req.set_accept_format('json') |
||||
req.set_RoleArn(self.sts_role_arn) |
||||
req.set_RoleSessionName(name[0:31]) |
||||
req.set_DurationSeconds(expires) |
||||
|
||||
body = clt.do_action_with_exception(req) |
||||
|
||||
j = json.loads(oss2.to_unicode(body)) |
||||
|
||||
self.token.access_key_id = j['Credentials']['AccessKeyId'] |
||||
self.token.access_key_secret = j['Credentials']['AccessKeySecret'] |
||||
self.token.security_token = j['Credentials']['SecurityToken'] |
||||
self.token.request_id = j['RequestId'] |
||||
self.token.expiration = oss2.utils.to_unixtime(j['Credentials']['Expiration'], '%Y-%m-%dT%H:%M:%SZ') |
||||
self.token.bucket=self.bucket_name |
||||
self.token.endpoint=self.endpoint |
||||
return self.token.__dict__ |
||||
|
||||
def get_upload_token(self,name,expires=1800): |
||||
return self.fetch_sts_token(name,expires) |
||||
|
||||
def get_auth_bucket(self,name,expires): |
||||
self.fetch_sts_token(name,expires) |
||||
auth = oss2.StsAuth(self.token.access_key_id, self.token.access_key_secret, self.token.security_token) |
||||
self.bucket = oss2.Bucket(auth, self.endpoint,self.bucket_name) |
||||
|
||||
def get_download_url(self,name,expires=1800): |
||||
# self.get_auth_bucket(name,expires) |
||||
private_url=self.bucket.sign_url('GET',name,expires) |
||||
print(private_url) |
||||
return private_url |
||||
|
||||
def del_file(self,name): |
||||
# self.fetch_sts_token(name,expires=300) |
||||
# auth = oss2.StsAuth(self.token.access_key_id, self.token.access_key_secret, self.token.security_token) |
||||
# bucket = oss2.Bucket(auth, self.endpoint,self.bucket_name) |
||||
return self.bucket.delete_object(name) |
||||
|
||||
|
@ -0,0 +1,34 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 3月 |
||||
# author: liuyu |
||||
# date: 2020/3/18 |
||||
''' |
||||
本地存储api |
||||
''' |
||||
from api.utils.TokenManager import DownloadToken |
||||
from fir_ser import settings |
||||
import os |
||||
|
||||
class LocalStorage(object): |
||||
def __init__(self,**kwargs): |
||||
self.domain_name = kwargs.get('domain_name') |
||||
|
||||
def get_upload_token(self,name,expires): |
||||
dtoken = DownloadToken() |
||||
return dtoken.make_token(name,expires) |
||||
|
||||
def get_download_url(self,name,expires=1800): |
||||
dtoken = DownloadToken() |
||||
base_url = '/'.join([self.domain_name,'download', name]) |
||||
download_url = base_url + "?token=" + dtoken.make_token(name,expires) |
||||
return download_url |
||||
|
||||
def del_file(self,name): |
||||
try: |
||||
os.remove(os.path.join(settings.MEDIA_ROOT, name)) |
||||
return True |
||||
except Exception as e: |
||||
print(e) |
||||
return False |
||||
|
@ -0,0 +1,60 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 3月 |
||||
# author: liuyu |
||||
# date: 2020/3/23 |
||||
|
||||
from api.models import AppStorage, UserInfo |
||||
from .aliyunApi import AliYunOss |
||||
from .qiniuApi import QiNiuOss |
||||
from .localApi import LocalStorage |
||||
import json |
||||
|
||||
|
||||
class Storage(object): |
||||
def __init__(self, user): |
||||
self.storage = self.get_storage(user) |
||||
|
||||
def get_upload_token(self, filename, expires=1800): |
||||
if self.storage: |
||||
return self.storage.get_upload_token(filename, expires) |
||||
|
||||
def get_download_url(self, filename, expires=1800): |
||||
if self.storage: |
||||
return self.storage.get_download_url(filename, expires) |
||||
|
||||
def delete_file(self, filename, apptype=None): |
||||
if self.storage: |
||||
if apptype is not None: |
||||
if apptype == 0: |
||||
filename = filename + '.apk' |
||||
else: |
||||
filename = filename + '.ipa' |
||||
return self.storage.del_file(filename) |
||||
|
||||
def get_storage(self, user): |
||||
storage_obj = user.storage |
||||
if storage_obj: |
||||
auth = self.get_storage_auth(storage_obj) |
||||
storage_type = storage_obj.storage_type |
||||
if storage_type == 1: |
||||
new_storage_obj = QiNiuOss(**auth) |
||||
elif storage_type == 2: |
||||
new_storage_obj = AliYunOss(**auth) |
||||
else: |
||||
new_storage_obj = LocalStorage(**auth) |
||||
return new_storage_obj |
||||
return None |
||||
|
||||
def get_storage_auth(self, storage_obj): |
||||
auth_dict = { |
||||
'access_key': storage_obj.access_key, |
||||
'secret_key': storage_obj.secret_key, |
||||
'bucket_name': storage_obj.bucket_name, |
||||
} |
||||
try: |
||||
additionalparameters = json.loads(storage_obj.additionalparameters) |
||||
except Exception as e: |
||||
print(e) |
||||
additionalparameters = {} |
||||
return {**auth_dict, **additionalparameters} |
@ -1,465 +0,0 @@ |
||||
from rest_framework.views import APIView |
||||
from api.utils.response import BaseResponse |
||||
from api.utils.auth import ExpiringTokenAuthentication |
||||
from rest_framework.response import Response |
||||
import json |
||||
import datetime |
||||
from django.conf import settings |
||||
from django_redis import get_redis_connection |
||||
from api.utils.exceptions import CommonException |
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
import redis |
||||
|
||||
REDIS_CONN = redis.Redis(decode_responses=True) |
||||
|
||||
|
||||
class AccountView(APIView): |
||||
''' |
||||
结算接口 |
||||
''' |
||||
authentication_classes = [ExpiringTokenAuthentication, ] |
||||
|
||||
def get_coupon_list(self, request, course_id=None): |
||||
|
||||
now = datetime.datetime.utcnow() |
||||
|
||||
coupon_record_list = CouponRecord.objects.filter( |
||||
account=request.user, |
||||
status=0, |
||||
coupon__valid_begin_date__lte=now, |
||||
coupon__valid_end_date__gt=now, |
||||
coupon__content_type_id=14, |
||||
coupon__object_id=course_id |
||||
|
||||
) |
||||
|
||||
coupon_list = [] |
||||
|
||||
for coupon_record in coupon_record_list: |
||||
coupon_list.append({ |
||||
|
||||
"pk": coupon_record.pk, |
||||
"name": coupon_record.coupon.name, |
||||
"coupon_type": coupon_record.coupon.get_coupon_type_display(), |
||||
"money_equivalent_value": coupon_record.coupon.money_equivalent_value, |
||||
"off_percent": coupon_record.coupon.off_percent, |
||||
"minimum_consume": coupon_record.coupon.minimum_consume, |
||||
}) |
||||
|
||||
return coupon_list |
||||
|
||||
def post(self, request, *args, **kwargs): |
||||
# 1 获取数据 |
||||
''' |
||||
course_list=[{ |
||||
"course_id":1, |
||||
"price_policy_id":2 |
||||
}, |
||||
|
||||
] |
||||
|
||||
:param request: |
||||
:param args: |
||||
:param kwargs: |
||||
:return: |
||||
''' |
||||
user = request.user |
||||
course_list = request.data |
||||
print("course_list",course_list) |
||||
print("course_list",type(course_list)) |
||||
response = BaseResponse() |
||||
try: |
||||
# 2 创建数据结构 |
||||
# 清空操作 |
||||
# 找到所有以account_userid_*,全部清空 |
||||
del_list = REDIS_CONN.keys(settings.ACCOUNT_KEY % (user.pk, "*")) |
||||
if del_list: |
||||
REDIS_CONN.delete(*del_list) |
||||
|
||||
price_list=[] |
||||
for course_dict in course_list: |
||||
|
||||
course_id=course_dict.get("course_id") |
||||
price_policy_id=course_dict.get("price_policy_id") |
||||
# 校验课程是否存在 |
||||
course_obj=Course.objects.get(pk=course_id) |
||||
# 查找课程关联的价格策略 |
||||
price_policy_list = course_obj.price_policy.all() |
||||
price_policy_dict = {} |
||||
for price_policy in price_policy_list: |
||||
price_policy_dict[price_policy.pk] = { |
||||
"prcie": price_policy.price, |
||||
"valid_period": price_policy.valid_period, |
||||
"valid_period_text": price_policy.get_valid_period_display(), |
||||
"default": price_policy.pk == price_policy_id |
||||
} |
||||
|
||||
if price_policy_id not in price_policy_dict: |
||||
raise CommonException(1001, "价格策略异常!") |
||||
pp=PricePolicy.objects.get(pk=price_policy_id) |
||||
# 将课程信息加入到每一个课程结算字典中 |
||||
account_dict = { |
||||
"id": course_id, |
||||
"name": course_obj.name, |
||||
"course_img": course_obj.course_img, |
||||
"relate_price_policy": price_policy_dict, |
||||
"default_price": pp.price, |
||||
"rebate_price": pp.price, |
||||
"default_price_period": pp.valid_period, |
||||
"default_price_policy_id": pp.pk |
||||
} |
||||
# 课程价格加入到价格列表 |
||||
price_list.append(float(pp.price)) |
||||
|
||||
# 查询当前用户拥有未使用的,在有效期的且与当前课程相关的优惠券 |
||||
account_dict["coupon_list"] = self.get_coupon_list(request, course_id) |
||||
|
||||
# 存储结算信息 |
||||
account_key = settings.ACCOUNT_KEY % (user.pk, course_id) |
||||
REDIS_CONN.set(account_key, json.dumps(account_dict)) |
||||
|
||||
# 获取通用优惠券,加入redis中 |
||||
|
||||
REDIS_CONN.set("global_coupon_%s" % user.pk, json.dumps(self.get_coupon_list(request))) |
||||
REDIS_CONN.set("total_price",sum(price_list)) |
||||
|
||||
except ObjectDoesNotExist as e: |
||||
response.code = 1001 |
||||
response.error = "课程不存在!" |
||||
except CommonException as e: |
||||
response.code = e.code |
||||
response.error = e.error |
||||
|
||||
# except Exception as e: |
||||
# response.code = 500 |
||||
# response.error = str(e) |
||||
|
||||
return Response(response.dict) |
||||
|
||||
def get(self, request, *args, **kwargs): |
||||
|
||||
res = BaseResponse() |
||||
try: |
||||
# 1 取到user_id |
||||
user_id = request.user.id |
||||
# 2 拼接购物车的key |
||||
account_key = settings.ACCOUNT_KEY % (user_id, "*") |
||||
# shopping_car_1_* |
||||
# shopping_car_1_asdgnlaksdj |
||||
# 3 去redis读取该用户的所有加入购物车的课程 |
||||
# 3.1 先去模糊匹配出所有符合要求的key |
||||
all_keys = REDIS_CONN.scan_iter(account_key) |
||||
|
||||
# 3.2 循环所有的keys 得到每个可以 |
||||
account_course_list = [] |
||||
for key in all_keys: |
||||
account_course = json.loads(REDIS_CONN.get(key)) |
||||
account_course_list.append(account_course) |
||||
|
||||
global_coupons = json.loads(REDIS_CONN.get("global_coupon_%s" % request.user.pk)) |
||||
total_price = REDIS_CONN.get("total_price") |
||||
res.data = { |
||||
"account_course_list": account_course_list, |
||||
"total": len(account_course_list), |
||||
"global_coupons": global_coupons, |
||||
"total_price": total_price |
||||
} |
||||
|
||||
except Exception as e: |
||||
res.code = 1033 |
||||
res.error = "获取购物车失败" |
||||
|
||||
return Response(res.dict) |
||||
|
||||
def cal_coupon_price(self,price,coupon_info): |
||||
|
||||
print("coupon_info",coupon_info) |
||||
coupon_type=coupon_info["coupon_type"] |
||||
money_equivalent_value=coupon_info.get("money_equivalent_value") |
||||
off_percent=coupon_info.get("off_percent") |
||||
minimum_consume=coupon_info.get("minimum_consume") |
||||
rebate_price=0 |
||||
if coupon_type == "立减券": # 立减券 |
||||
rebate_price=price-money_equivalent_value |
||||
if rebate_price <= 0: |
||||
rebate_price=0 |
||||
elif coupon_type == "满减券": # 满减券 |
||||
if minimum_consume > price: |
||||
raise CommonException(3000,"优惠券未达到最低消费") |
||||
else: |
||||
rebate_price=price-money_equivalent_value |
||||
elif coupon_type == "折扣券": |
||||
rebate_price=price*off_percent/100 |
||||
|
||||
return rebate_price |
||||
|
||||
def put(self,request, *args, **kwargs): |
||||
''' |
||||
choose_coupons: |
||||
{ |
||||
choose_coupons={"1":2,"2":3,"global_coupon_id":5} |
||||
is_beli:true |
||||
} |
||||
''' |
||||
res=BaseResponse() |
||||
# try: |
||||
|
||||
# 1 获取数据 |
||||
choose_coupons=request.data.get("choose_coupons") |
||||
is_beli=request.data.get("is_beli") |
||||
user_pk=request.user.pk |
||||
|
||||
# 2 获取结算课程列表 |
||||
cal_price={} |
||||
data=self.get(request).data.get("data") |
||||
account_course_list=data.get("account_course_list") |
||||
print("account_course_list",account_course_list) |
||||
''' |
||||
account_course_list=[{ |
||||
'id': 4, |
||||
'coupon_list': [{ |
||||
'off_percent': None, |
||||
'pk': 4, |
||||
'money_equivalent_value': 300.0, |
||||
'coupon_type': '立减券', |
||||
'minimum_consume': 0, |
||||
'name': '51劳动节' |
||||
}], |
||||
'course_img': 'https://luffycity.com/static/frontend/course/12/Linux5周入门_1509589530.6144893.png', |
||||
'default_price': 1500.0, |
||||
'default_price_period': 60, |
||||
'relate_price_policy': { |
||||
'5': { |
||||
'valid_period_text': '2个月', |
||||
'default': True, |
||||
'valid_period': 60, |
||||
'prcie': 1500.0 |
||||
}, |
||||
'4': { |
||||
'valid_period_text': '1个月', |
||||
'default': False, |
||||
'valid_period': 30, |
||||
'prcie': 1000.0 |
||||
} |
||||
}, |
||||
'default_price_policy_id': 5, |
||||
'name': 'Linux系统基础5周入门精讲' |
||||
}, { |
||||
'id': 2, |
||||
'coupon_list': [{ |
||||
'off_percent': 80, |
||||
'pk': 3, |
||||
'money_equivalent_value': 0.0, |
||||
'coupon_type': '折扣券', |
||||
'minimum_consume': 0, |
||||
'name': '清明节活动' |
||||
}], |
||||
'course_img': 'https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png', |
||||
'default_price': 300.0, |
||||
'default_price_period': 30, |
||||
'relate_price_policy': { |
||||
'3': { |
||||
'valid_period_text': '1个月', |
||||
'default': True, |
||||
'valid_period': 30, |
||||
'prcie': 300.0 |
||||
}, |
||||
'1': { |
||||
'valid_period_text': '1周', |
||||
'default': False, |
||||
'valid_period': 7, |
||||
'prcie': 100.0 |
||||
}, |
||||
'2': { |
||||
'valid_period_text': '2周', |
||||
'default': False, |
||||
'valid_period': 14, |
||||
'prcie': 200.0 |
||||
} |
||||
}, |
||||
'default_price_policy_id': 3, |
||||
'name': 'Django框架学习' |
||||
}] |
||||
''' |
||||
account_courses_info={} |
||||
for account_course in account_course_list: |
||||
temp={ |
||||
"coupon":{}, |
||||
"default_price":account_course["default_price"] |
||||
} |
||||
account_courses_info[account_course["id"]]=temp |
||||
|
||||
for item in account_course["coupon_list"]: |
||||
|
||||
print("choose_coupons",choose_coupons) # {'4': 4} |
||||
print(str(account_course["id"])) |
||||
|
||||
coupon_id=choose_coupons.get(str(account_course["id"])) |
||||
if coupon_id == item["pk"]: |
||||
temp["coupon"]=item |
||||
|
||||
print("account_course_info",account_courses_info) |
||||
price_list=[] |
||||
total_price=0 |
||||
''' |
||||
{ |
||||
2: { |
||||
'coupon': { |
||||
'money_equivalent_value': 0.0, |
||||
'name': '清明节活动', |
||||
'pk': 3, |
||||
'off_percent': 80, |
||||
'coupon_type': '折扣券', |
||||
'minimum_consume': 0 |
||||
}, |
||||
'default_price': 200.0 |
||||
} |
||||
} |
||||
''' |
||||
for key,val in account_courses_info.items(): |
||||
if not val.get("coupon"): |
||||
price_list.append(val["default_price"]) |
||||
cal_price[key]=val["default_price"] |
||||
else: |
||||
coupon_info=val.get("coupon") |
||||
default_price=val["default_price"] |
||||
rebate_price=self.cal_coupon_price(default_price,coupon_info) |
||||
price_list.append(rebate_price) |
||||
cal_price[key]=rebate_price |
||||
|
||||
print("课程优惠券后价格列表price_list",price_list) |
||||
total_price=sum(price_list) |
||||
# 3 计算通用优惠券的价格 |
||||
global_coupon_id=choose_coupons.get("global_coupon_id") |
||||
if global_coupon_id: |
||||
|
||||
global_coupons=data.get("global_coupons") |
||||
print("global_coupons",global_coupons) |
||||
global_coupon_dict={} |
||||
for item in global_coupons: |
||||
global_coupon_dict[item["pk"]]=item |
||||
total_price=self.cal_coupon_price(total_price,global_coupon_dict[global_coupon_id]) |
||||
print("通用优惠券",global_coupon_dict[global_coupon_id]["coupon_type"]) |
||||
print("计算后total_price=",total_price) |
||||
|
||||
# 计算贝里 |
||||
if json.loads(is_beli): |
||||
print("request.user.beli",request.user.beli) |
||||
total_price=total_price-request.user.beli/10 |
||||
if total_price<0: |
||||
total_price=0 |
||||
print("贝里数计算后",total_price) |
||||
|
||||
cal_price["total_price"]=total_price |
||||
res.data=cal_price |
||||
|
||||
# except Exception as e: |
||||
# res.code=500 |
||||
# res.msg="结算错误!"+str(e) |
||||
|
||||
return Response(res.dict) |
||||
|
||||
|
||||
''' |
||||
1 结算中心post添加接口数据结构: |
||||
{ |
||||
"course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", |
||||
"coupon_list": [{ |
||||
"name": "清明节活动", |
||||
"minimum_consume": 0, |
||||
"money_equivalent_value": 0.0, |
||||
"off_percent": 80, |
||||
"pk": 3, |
||||
"coupon_type": "折扣券" |
||||
}], |
||||
"relate_price_policy": { |
||||
"1": { |
||||
"valid_period": 7, |
||||
"valid_period_text": "1周", |
||||
"prcie": 100.0, |
||||
"default": true |
||||
}, |
||||
"2": { |
||||
"valid_period": 14, |
||||
"valid_period_text": "2周", |
||||
"prcie": 200.0, |
||||
"default": false |
||||
}, |
||||
"3": { |
||||
"valid_period": 30, |
||||
"valid_period_text": "1个月", |
||||
"prcie": 300.0, |
||||
"default": false |
||||
} |
||||
}, |
||||
"name": "Django框架学习", |
||||
"default_price": 100.0, |
||||
"id": 2, |
||||
"default_price_period": 7, |
||||
"default_price_policy_id": 1 |
||||
} |
||||
|
||||
|
||||
2 结算中心get查询接口: |
||||
|
||||
{ |
||||
"data": { |
||||
"total": 1, |
||||
"global_coupons": [ |
||||
{ |
||||
"name": "国庆节活动通用券", |
||||
"coupon_type": "满减券", |
||||
"minimum_consume": 100, |
||||
"money_equivalent_value": 50, |
||||
"off_percent": null, |
||||
"pk": 2 |
||||
} |
||||
], |
||||
"total_price": "100.0", |
||||
"account_course_list": [ |
||||
{ |
||||
"course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", |
||||
"name": "Django框架学习", |
||||
"relate_price_policy": { |
||||
"1": { |
||||
"valid_period": 7, |
||||
"prcie": 100, |
||||
"valid_period_text": "1周", |
||||
"default": true |
||||
}, |
||||
"2": { |
||||
"valid_period": 14, |
||||
"prcie": 200, |
||||
"valid_period_text": "2周", |
||||
"default": false |
||||
}, |
||||
"3": { |
||||
"valid_period": 30, |
||||
"prcie": 300, |
||||
"valid_period_text": "1个月", |
||||
"default": false |
||||
} |
||||
}, |
||||
"coupon_list": [ |
||||
{ |
||||
"name": "清明节活动", |
||||
"coupon_type": "折扣券", |
||||
"minimum_consume": 0, |
||||
"money_equivalent_value": 0, |
||||
"off_percent": 80, |
||||
"pk": 3 |
||||
} |
||||
], |
||||
"default_price": 100, |
||||
"id": 2, |
||||
"default_price_period": 7, |
||||
"default_price_policy_id": 1 |
||||
} |
||||
] |
||||
}, |
||||
"code": 1000, |
||||
"msg": "" |
||||
} |
||||
|
||||
|
||||
''' |
@ -1,41 +0,0 @@ |
||||
from api import models |
||||
from api.utils import serializer |
||||
|
||||
from rest_framework.response import Response |
||||
from rest_framework.viewsets import ModelViewSet |
||||
from api.utils.auth import ExpiringTokenAuthentication |
||||
from api.utils.filter import CourseFilter |
||||
|
||||
|
||||
class CourseView(ModelViewSet): |
||||
# authentication_classes = [ExpiringTokenAuthentication,] |
||||
queryset = models.Course.objects.all() |
||||
serializer_class = serializer.CourseSerializer |
||||
filter_backends = [CourseFilter,] |
||||
def list(self, request, *args, **kwargs): |
||||
queryset = self.filter_queryset(self.get_queryset()) |
||||
|
||||
page = self.paginate_queryset(queryset) |
||||
if page is not None: |
||||
serializer = self.get_serializer(page, many=True) |
||||
return self.get_paginated_response(serializer.data) |
||||
|
||||
serializer = self.get_serializer(queryset, many=True) |
||||
|
||||
return Response({"code": 0, "data": serializer.data}) |
||||
|
||||
|
||||
class CourseDetailView(ModelViewSet): |
||||
queryset = models.CourseDetail.objects.all() |
||||
serializer_class = serializer.CourseDetailSerializer |
||||
|
||||
|
||||
|
||||
class CourseCategoryView(ModelViewSet): |
||||
queryset = models.CourseCategory.objects.all() |
||||
serializer_class = serializer.CourseCategorySerializer |
||||
|
||||
def list(self, request, *args, **kwargs): |
||||
queryset = self.filter_queryset(self.get_queryset()) |
||||
serializer = self.get_serializer(queryset, many=True) |
||||
return Response({"error_no":0,"data":serializer.data}) |
@ -1,42 +0,0 @@ |
||||
from rest_framework.views import APIView |
||||
from rest_framework.response import Response |
||||
from api.utils.auth import ExpiringTokenAuthentication |
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from api.utils.response import BaseResponse |
||||
from api.models import Course, CouponRecord, Coupon, PricePolicy, Order, OrderDetail |
||||
from api.utils.exceptions import CommonException |
||||
import datetime |
||||
import uuid |
||||
import time |
||||
import json |
||||
|
||||
from api.models import Order, OrderDetail |
||||
|
||||
|
||||
class OrderView(APIView): |
||||
authentication_classes = [ExpiringTokenAuthentication] |
||||
|
||||
def get(self, request, *args, **kwargs): |
||||
res = BaseResponse() |
||||
order_list = Order.objects.filter(account=request.user).order_by("-date") |
||||
data = [] |
||||
for order in order_list: |
||||
data.append({ |
||||
"order_number": order.order_number, |
||||
"date": order.date.strftime("%Y-%m-%d %H:%M:%S"), |
||||
"status": order.get_status_display(), |
||||
"actual_amount": order.actual_amount, |
||||
"orderdetail_list": [{ |
||||
"original_price": obj.original_price, |
||||
"price": obj.price, |
||||
"course_name": obj.content_object.name, |
||||
"valid_period_display": obj.valid_period_display, |
||||
} for obj in order.orderdetail_set.all()] |
||||
}) |
||||
|
||||
print("data", data) |
||||
res.data = data |
||||
|
||||
return Response(res.dict) |
||||
|
||||
|
@ -1,263 +0,0 @@ |
||||
from rest_framework.views import APIView |
||||
from rest_framework.response import Response |
||||
from django.http import JsonResponse |
||||
from api.utils.auth import ExpiringTokenAuthentication |
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from api.utils.response import BaseResponse |
||||
from api.models import Course, CouponRecord, Coupon, PricePolicy,Order,OrderDetail |
||||
from api.utils.exceptions import CommonException |
||||
|
||||
import random,datetime,time,os,sys |
||||
from api.utils.ali.api import ali_api |
||||
|
||||
import redis |
||||
from django.conf import settings |
||||
|
||||
REDIS_CONN = redis.Redis(decode_responses=True) |
||||
|
||||
class PaymentView(APIView): |
||||
''' |
||||
模拟请求数据格式: |
||||
|
||||
{ |
||||
is_beli:true, |
||||
course_list=[ |
||||
{ course_id:1 |
||||
default_price_policy_id:1, |
||||
coupon_record_id:2 |
||||
}, |
||||
{ course_id:2 |
||||
default_price_policy_id:4, |
||||
coupon_record_id:6 |
||||
} |
||||
], |
||||
global_coupon_id:3, |
||||
pay_money:298 |
||||
|
||||
} |
||||
|
||||
|
||||
状态码: |
||||
1000: 成功 |
||||
1001: 课程不存在 |
||||
1002: 价格策略不合法 |
||||
1003: 加入购物车失败 |
||||
1004: 获取购物车失败 |
||||
1005: 贝里数有问题 |
||||
1006: 优惠券异常 |
||||
1007: 优惠券未达到最低消费 |
||||
1008: 支付总价格异常 |
||||
|
||||
''' |
||||
authentication_classes = [ExpiringTokenAuthentication] |
||||
|
||||
def get_pay_url(self,request,order_number,final_price): |
||||
|
||||
# 4 调用支付宝支付接口(二维码页面) |
||||
|
||||
if request.META["HTTP_USER_AGENT"]: |
||||
|
||||
pay_api = ali_api.pay.pc |
||||
|
||||
elif request == "APP": |
||||
pay_api = ali_api.pay.app |
||||
|
||||
else: |
||||
pay_api = ali_api.pay.wap |
||||
|
||||
|
||||
pay_url = pay_api.direct( |
||||
subject="路飞学城", # 商品简单描述 |
||||
out_trade_no=order_number, # 商户订单号 |
||||
total_amount=final_price, # 交易金额(单位: 元 保留俩位小数) |
||||
) |
||||
|
||||
|
||||
print("pay_url",pay_url) |
||||
return pay_url |
||||
|
||||
def get_order_num(self): |
||||
now=datetime.datetime.now() |
||||
orderType="1" |
||||
dateStr4yyyyMMddHHmmss="{0}{1}{2}".format(now.year,now.month,now.day) |
||||
rand=str(random.randint(1000,9999)) |
||||
|
||||
s=orderType+dateStr4yyyyMMddHHmmss+rand |
||||
|
||||
return s |
||||
|
||||
def post(self, request, *args, **kwargs): |
||||
print(request.data) |
||||
response = BaseResponse() |
||||
# 1 获取数据 |
||||
user_id = request.user.pk |
||||
global_coupon_id = request.data.get("global_coupon_id") |
||||
pay_money = request.data.get("pay_money") |
||||
course_list = request.data.get("course_list") |
||||
is_beli = request.data.get("is_beli") |
||||
now = datetime.datetime.now() |
||||
try: |
||||
# 2 校验数据 |
||||
# 2.2 校验课程 |
||||
course_price_list = [] |
||||
for course_dict in course_list: |
||||
# 2.2.1 校验课程id |
||||
|
||||
course_id = course_dict.get("course_id") |
||||
print("course_id",course_id) |
||||
course_obj = Course.objects.get(pk=course_id) |
||||
# 2.2.2 价格策略id |
||||
if course_dict.get("default_price_policy_id") not in [obj.pk for obj in course_obj.price_policy.all()]: |
||||
raise CommonException("价格策略异常!", 1002) |
||||
# 2.2.3 课程优惠券id |
||||
|
||||
price_policy_obj = PricePolicy.objects.get(pk=course_dict.get("default_price_policy_id")) |
||||
course_dict["original_price"]=price_policy_obj.price |
||||
course_dict["valid_period_display"]=price_policy_obj.get_valid_period_display() |
||||
course_dict["valid_period"]=price_policy_obj.valid_period |
||||
coupon_record_id = course_dict.get("coupon_record_id") |
||||
if coupon_record_id: |
||||
|
||||
coupon_record_list = CouponRecord.objects.filter(account=request.user, |
||||
status=0, |
||||
coupon__valid_begin_date__lt=now, |
||||
coupon__valid_end_date__gt=now, |
||||
coupon__content_type_id=14, |
||||
coupon__object_id=course_id |
||||
) |
||||
print("coupon_record_id",coupon_record_id) |
||||
|
||||
if coupon_record_id and coupon_record_id not in [obj.pk for obj in coupon_record_list]: |
||||
raise CommonException("课程优惠券异常!", 1006) |
||||
|
||||
# 计算循环课程的课程优惠券优惠后的价格 |
||||
coupon_record_obj = CouponRecord.objects.get(pk=coupon_record_id) |
||||
rebate_price = self.cal_coupon_price(price_policy_obj.price, coupon_record_obj) |
||||
course_price_list.append(rebate_price) |
||||
course_dict["rebate_price"]=rebate_price |
||||
else: |
||||
course_price_list.append(price_policy_obj.price) |
||||
|
||||
# 2.3 校验通用优惠券id |
||||
global_coupon_record_list = CouponRecord.objects.filter(account=request.user, |
||||
status=0, |
||||
coupon__valid_begin_date__lt=now, |
||||
coupon__valid_end_date__gt=now, |
||||
coupon__content_type_id=14, |
||||
coupon__object_id=None |
||||
) |
||||
if global_coupon_id and global_coupon_id not in [obj.pk for obj in global_coupon_record_list]: |
||||
raise CommonException("通用优惠券异常", 1006) |
||||
|
||||
if global_coupon_id: |
||||
global_coupon_record_obj = CouponRecord.objects.get(pk=global_coupon_id) |
||||
final_price = self.cal_coupon_price(sum(course_price_list), global_coupon_record_obj) |
||||
else: |
||||
final_price=sum(course_price_list) |
||||
# 2.4 计算实际支付价格与money做校验 |
||||
cost_beli_num=0 |
||||
if is_beli: |
||||
final_price = final_price - request.user.beli / 10 |
||||
cost_beli_num=request.user.beli |
||||
if final_price < 0: |
||||
final_price = 0 |
||||
cost_beli_num=final_price*10 |
||||
print(final_price) |
||||
|
||||
if final_price != float(pay_money): |
||||
raise CommonException(1008, "支付总价格异常!") |
||||
|
||||
# 3 生成订单记录 |
||||
# Order记录 |
||||
# orderDetail |
||||
# orderDetail |
||||
# orderDetail |
||||
order_number=self.get_order_num() |
||||
print("order_number",order_number) |
||||
order_obj=Order.objects.create( |
||||
payment_type=1, |
||||
order_number=order_number, |
||||
account=request.user, |
||||
status=1, |
||||
order_type=1, |
||||
actual_amount=pay_money, |
||||
) |
||||
print("course_list",course_list) |
||||
|
||||
for course_item in course_list: |
||||
OrderDetail.objects.create( |
||||
order=order_obj, |
||||
content_type_id=14, |
||||
object_id=course_item.get("course_id"), |
||||
original_price=course_item.get("original_price"), |
||||
price=course_item.get("rebate_price") or course_item.get("original_price"), |
||||
valid_period=course_item.get("valid_period"), |
||||
valid_period_display=course_item.get("valid_period_display"), |
||||
) |
||||
|
||||
request.user.beli=request.user.beli-cost_beli_num |
||||
request.user.save() |
||||
REDIS_CONN.set(order_number+"|"+str(cost_beli_num),"",20) |
||||
account_key=settings.ACCOUNT_KEY%(user_id,"*") |
||||
REDIS_CONN.delete(*REDIS_CONN.keys(account_key)) |
||||
''' |
||||
[ |
||||
{ course_id:1 |
||||
default_price_policy_id:1, |
||||
coupon_record_id:2 |
||||
}, |
||||
{ |
||||
course_id:2 |
||||
default_price_policy_id:4, |
||||
coupon_record_id:6 |
||||
} |
||||
] |
||||
''' |
||||
|
||||
|
||||
response.data = self.get_pay_url(request,order_number,final_price) |
||||
|
||||
except ObjectDoesNotExist as e: |
||||
response.code = 1001 |
||||
response.msg = "课程不存在!" |
||||
except CommonException as e: |
||||
response.code = e.code |
||||
response.msg = e.error |
||||
except Exception as e: |
||||
response.code = 500 |
||||
response.msg = str(e) |
||||
|
||||
return Response(response.dict) |
||||
|
||||
def cal_coupon_price(self, price, coupon_record_obj): |
||||
coupon_type = coupon_record_obj.coupon.coupon_type |
||||
money_equivalent_value = coupon_record_obj.coupon.money_equivalent_value |
||||
off_percent = coupon_record_obj.coupon.off_percent |
||||
minimum_consume = coupon_record_obj.coupon.minimum_consume |
||||
rebate_price = 0 |
||||
if coupon_type == 0: # 立减券 |
||||
rebate_price = price - money_equivalent_value |
||||
if rebate_price <= 0: |
||||
rebate_price = 0 |
||||
elif coupon_type == 1: # 满减券 |
||||
if minimum_consume > price: |
||||
raise CommonException(1007, "优惠券未达到最低消费") |
||||
else: |
||||
rebate_price = price - money_equivalent_value |
||||
elif coupon_type == 2: |
||||
rebate_price = price * off_percent / 100 |
||||
|
||||
return rebate_price |
||||
|
||||
|
||||
def get_pay_url(request): |
||||
print('--->',request.GET.get("order_number")) |
||||
pay_url = ali_api.pay.pc.direct( |
||||
subject="python全栈课程", # 商品简单描述 |
||||
out_trade_no=request.GET.get("order_number"), # 商户订单号 |
||||
total_amount=request.GET.get("final_price"), # 交易金额(单位: 元 保留俩位小数) |
||||
) |
||||
print("pay_url",pay_url) |
||||
|
||||
return JsonResponse({"pay_url":pay_url}) |
||||
|
@ -1,258 +0,0 @@ |
||||
om rest_framework.views import APIView |
||||
from rest_framework.viewsets import ViewSetMixin, ModelViewSet |
||||
|
||||
from api.utils.response import BaseResponse |
||||
|
||||
from rest_framework.response import Response |
||||
|
||||
from api.models import Course, CourseDetail, PricePolicy |
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from api.utils.exceptions import CommonException |
||||
from django_redis import get_redis_connection |
||||
from django.conf import settings |
||||
from api.utils.auth import ExpiringTokenAuthentication |
||||
|
||||
import json |
||||
from api.utils.permission import LoginUserPermission |
||||
import redis |
||||
|
||||
REDIS_CONN = redis.Redis(decode_responses=True) |
||||
|
||||
|
||||
class ShoppingCarView(APIView): |
||||
""" |
||||
1030 加入购物车失败 |
||||
""" |
||||
authentication_classes = [ExpiringTokenAuthentication, ] |
||||
|
||||
def post(self, request): |
||||
res = BaseResponse() |
||||
try: |
||||
# 1 获取前端传过来的course_id 以及price_policy_id user_id |
||||
course_id = request.data.get("course_id", "") |
||||
price_policy_id = request.data.get("price_policy_id", "") |
||||
user_id = request.user.id |
||||
# 2 验证数据的合法性 |
||||
# 2.1 验证course_id是否合法 |
||||
course_obj = Course.objects.get(pk=course_id) |
||||
# 2.2 校验价格策略是否能合法 |
||||
|
||||
# 查找课程关联的价格策略 |
||||
price_policy_list = course_obj.price_policy.all() |
||||
price_policy_dict = {} |
||||
for price_policy in price_policy_list: |
||||
price_policy_dict[price_policy.pk] = { |
||||
"prcie": price_policy.price, |
||||
"valid_period": price_policy.valid_period, |
||||
"valid_period_text": price_policy.get_valid_period_display(), |
||||
"default": price_policy.pk == price_policy_id |
||||
} |
||||
|
||||
if price_policy_id not in price_policy_dict: |
||||
raise CommonException(1001, "价格策略异常!") |
||||
|
||||
# 3 构建我们想要的数据结构 |
||||
# 价格策略对象 |
||||
pp = PricePolicy.objects.get(pk=price_policy_id) |
||||
course_info = { |
||||
"id": course_id, |
||||
"name": course_obj.name, |
||||
"course_img": course_obj.course_img, |
||||
"relate_price_policy": price_policy_dict, |
||||
"default_price": pp.price, |
||||
"default_price_period": pp.valid_period, |
||||
"default_price_policy_id": pp.pk |
||||
|
||||
} |
||||
|
||||
# 4 写入redis |
||||
# 4.1 先拼接购物车的key |
||||
shopping_car_key = settings.SHOPPING_CAR_KEY % (user_id, course_id) |
||||
# 4.2 写入redis |
||||
print("------>") |
||||
REDIS_CONN.set(shopping_car_key, json.dumps(course_info)) |
||||
res.msg = "加入购物车成功!" |
||||
|
||||
except CommonException as e: |
||||
res.code = e.code |
||||
res.error = e.error |
||||
except Exception as e: |
||||
res.code = 1030 |
||||
res.error = "加入购物车失败!" |
||||
|
||||
return Response(res.dict) |
||||
|
||||
def get(self, request): |
||||
res = BaseResponse() |
||||
try: |
||||
# 1 取到user_id |
||||
user_id = request.user.id |
||||
# 2 拼接购物车的key |
||||
shopping_car_key = settings.SHOPPING_CAR_KEY % (user_id, "*") |
||||
print("shopping_car_key",shopping_car_key) |
||||
# shopping_car_1_* |
||||
# shopping_car_1_asdgnlaksdj |
||||
# 3 去redis读取该用户的所有加入购物车的课程 |
||||
# 3.1 先去模糊匹配出所有符合要求的key |
||||
all_keys = REDIS_CONN.keys(shopping_car_key) |
||||
print("all_keys",all_keys) |
||||
# 3.2 循环所有的keys 得到每个可以 |
||||
shopping_car_list = [] |
||||
for key in all_keys: |
||||
print("key",key) |
||||
course_info = json.loads(REDIS_CONN.get(key)) |
||||
shopping_car_list.append(course_info) |
||||
|
||||
res.data = {"shopping_car_list": shopping_car_list, "total": len(shopping_car_list)} |
||||
|
||||
except Exception as e: |
||||
res.code = 1033 |
||||
res.error = "获取购物车失败" |
||||
print("res.dict",res.dict) |
||||
return Response(res.dict) |
||||
|
||||
def put(self, request): |
||||
res = BaseResponse() |
||||
try: |
||||
# 1 获取前端传过来的course_id 以及price_policy_id |
||||
course_id = request.data.get("course_id", "") |
||||
price_policy_id = request.data.get("price_policy_id", "") |
||||
user_id = request.user.id |
||||
# 2 校验数据的合法性 |
||||
# 2.1 校验course_id是否合法 |
||||
shopping_car_key = settings.SHOPPING_CAR_KEY % (user_id, course_id) |
||||
if not REDIS_CONN.exists(shopping_car_key): |
||||
res.code = 1035 |
||||
res.error = "课程不存在" |
||||
return Response(res.dict) |
||||
# 2.2 判断价格策略是否合法 |
||||
course_info = REDIS_CONN.hgetall(shopping_car_key) |
||||
price_policy_dict = json.loads(course_info["price_policy_dict"]) |
||||
if str(price_policy_id) not in price_policy_dict: |
||||
res.code = 1036 |
||||
res.error = "所选的价格策略不存在" |
||||
return Response(res.dict) |
||||
# 3 修改redis中的default_policy_id |
||||
course_info["default_policy_id"] = price_policy_id |
||||
# 4 修改信息后写入redis |
||||
REDIS_CONN.hmset(shopping_car_key, course_info) |
||||
res.data = "更新成功" |
||||
except Exception as e: |
||||
res.code = 1034 |
||||
res.error = "更新价格策略失败" |
||||
return Response(res.dict) |
||||
|
||||
def delete(self, request): |
||||
res = BaseResponse() |
||||
try: |
||||
# 获取前端传过来的course_id |
||||
course_id = request.data.get("course_id", "") |
||||
user_id = request.user.id |
||||
# 判断课程id是否合法 |
||||
shopping_car_key = settings.SHOPPING_CAR_KEY % (user_id, course_id) |
||||
if not REDIS_CONN.exists(shopping_car_key): |
||||
res.code = 1039 |
||||
res.error = "删除的课程不存在" |
||||
return Response(res.dict) |
||||
# 删除redis中的数据 |
||||
REDIS_CONN.delete(shopping_car_key) |
||||
res.data = "删除成功" |
||||
except Exception as e: |
||||
res.code = 1037 |
||||
res.error = "删除失败" |
||||
return Response(res.dict) |
||||
|
||||
|
||||
''' |
||||
1 post接口构建数据结构: |
||||
{ |
||||
"id": 2, |
||||
"default_price_period": 14, |
||||
"relate_price_policy": { |
||||
"1": { |
||||
"valid_period": 7, |
||||
"valid_period_text": "1周", |
||||
"default": false, |
||||
"prcie": 100.0 |
||||
}, |
||||
"2": { |
||||
"valid_period": 14, |
||||
"valid_period_text": "2周", |
||||
"default": true, |
||||
"prcie": 200.0 |
||||
}, |
||||
"3": { |
||||
"valid_period": 30, |
||||
"valid_period_text": "1个月", |
||||
"default": false, |
||||
"prcie": 300.0 |
||||
} |
||||
}, |
||||
"name": "Django框架学习", |
||||
"course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", |
||||
"default_price": 200.0 |
||||
} |
||||
|
||||
|
||||
|
||||
2 get接口查询数据结构: |
||||
|
||||
{ |
||||
"data": { |
||||
"total": 2, |
||||
"shopping_car_list": [ |
||||
{ |
||||
"id": 2, |
||||
"default_price_period": 14, |
||||
"relate_price_policy": { |
||||
"1": { |
||||
"valid_period": 7, |
||||
"valid_period_text": "1周", |
||||
"default": false, |
||||
"prcie": 100 |
||||
}, |
||||
"2": { |
||||
"valid_period": 14, |
||||
"valid_period_text": "2周", |
||||
"default": true, |
||||
"prcie": 200 |
||||
}, |
||||
"3": { |
||||
"valid_period": 30, |
||||
"valid_period_text": "1个月", |
||||
"default": false, |
||||
"prcie": 300 |
||||
} |
||||
}, |
||||
"name": "Django框架学习", |
||||
"course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", |
||||
"default_price": 200 |
||||
}, |
||||
{ |
||||
"id": 4, |
||||
"default_price_period": 30, |
||||
"relate_price_policy": { |
||||
"4": { |
||||
"valid_period": 30, |
||||
"valid_period_text": "1个月", |
||||
"default": true, |
||||
"prcie": 1000 |
||||
}, |
||||
"5": { |
||||
"valid_period": 60, |
||||
"valid_period_text": "2个月", |
||||
"default": false, |
||||
"prcie": 1500 |
||||
} |
||||
}, |
||||
"name": "Linux系统基础5周入门精讲", |
||||
"course_img": "https://luffycity.com/static/frontend/course/12/Linux5周入门_1509589530.6144893.png", |
||||
"default_price": 1000 |
||||
} |
||||
] |
||||
}, |
||||
"code": 1000, |
||||
"msg": "" |
||||
} |
||||
|
||||
''' |
@ -1,114 +0,0 @@ |
||||
|
||||
from rest_framework.views import APIView |
||||
from api.utils.ali.api import ali_api |
||||
from api.utils.ali.tools import verify_signature |
||||
from rest_framework.response import Response |
||||
from api.models import Order |
||||
import datetime,time |
||||
from django.shortcuts import HttpResponse,redirect |
||||
from api.utils.response import BaseResponse |
||||
|
||||
class AlipayTradeView(APIView): |
||||
|
||||
def get(self,request,*args,**kwargs): |
||||
|
||||
try: |
||||
processed_dict = {} |
||||
for key, value in self.request.query_params.items(): |
||||
processed_dict[key] = value |
||||
# 校验签名 |
||||
verify_result = verify_signature(processed_dict, ali_api.pay.ali_public_key) |
||||
|
||||
if not verify_result: |
||||
return HttpResponse("sign is invalid") |
||||
|
||||
out_trade_no = processed_dict.get("out_trade_no") # 商户订单号 |
||||
redirect_to = "{0}?order_num={1}".format("http://47.94.172.250:8804/order/pay_success",out_trade_no) |
||||
return redirect(redirect_to) |
||||
|
||||
except Exception as e: |
||||
return HttpResponse("fail!") |
||||
|
||||
|
||||
def post(self, request): |
||||
""" |
||||
处理支付宝的notify_url |
||||
|
||||
支付宝对应交易的四种状态: |
||||
1,WAIT_BUYER_PAY 交易创建,等待买家付款 |
||||
2,TRADE_CLOSED 未付款交易超时关闭,或支付完成后全额退款 |
||||
3,TRADE_SUCCESS 交易支付成功 |
||||
4,TRADE_FINISHED 交易结束,不可退款 |
||||
|
||||
如果支付成功, 将要处理的事件: |
||||
1, 订单状态更改为交易完成(变更状态后, 通过信号进行如下操作) |
||||
2, 如果使用优惠券, 支付成功将要把优惠券的状态更改为已使用 |
||||
3, 如果使用余额, 将要进行扣除 |
||||
""" |
||||
processed_dict = {} |
||||
for key, value in self.request.data.items(): |
||||
processed_dict[key] = value |
||||
# 校验签名 |
||||
verify_result = verify_signature(processed_dict, ali_api.pay.ali_public_key) |
||||
|
||||
if not verify_result: |
||||
return Response("fail") |
||||
|
||||
order_sn = processed_dict.get("out_trade_no", "") # 商户网站唯一订单号 |
||||
trade_no = processed_dict.get("trade_no", "") # 该交易在支付宝系统中的交易流水号。最长64位 |
||||
trade_status = processed_dict.get("trade_status", "") # 支付宝系统的交易状态 |
||||
# 支付成功 |
||||
# 为放止支付宝重复请求, 获取到订单号并查询该订单的状态是否为交易完成, 如果交易完成, 即直接返回成功信号 |
||||
# 为该用户创建报名课程, 创建报名时间及结束时间 |
||||
# 区分LuffyX课程 和 付费课程 |
||||
# 当前策略LuffyX课程是不能加入购物车的 |
||||
if trade_status == "TRADE_SUCCESS": |
||||
gmt_payment = processed_dict.get("gmt_payment") # 买家付款时间 格式 yyyy-MM-dd HH:mm:ss |
||||
passback_params = processed_dict.get("passback_params", "{}") # 公共回传参数 |
||||
|
||||
# 修改订单状态 |
||||
save_status = self.change_order_status(order_sn, trade_no, gmt_payment, "alipay",passback_params) |
||||
if save_status is True: |
||||
return Response("success") |
||||
return Response("fail") |
||||
|
||||
|
||||
def change_order_status(order_num, payment_number, gmt_payment, trade_type, extra_params): |
||||
"""交易成功修改订单相关的状态 |
||||
|
||||
Parameters |
||||
---------- |
||||
order_num : string |
||||
订单号 |
||||
|
||||
payment_number : string or None |
||||
第三方订单号 |
||||
|
||||
gmt_payment : string |
||||
交易时间(要根据不同的交易方式格式化交易时间) |
||||
|
||||
trade_type: string |
||||
交易方式 |
||||
|
||||
extra_params: string json |
||||
交易回传参数 |
||||
|
||||
Returns |
||||
------- |
||||
bool |
||||
""" |
||||
try: |
||||
exist_order = Order.objects.get(order_number=order_num) |
||||
pay_time = datetime.datetime.strptime(gmt_payment, "%Y-%m-%d %H:%M:%S") |
||||
|
||||
if exist_order.status == 0: |
||||
return True |
||||
# 变更订单状态 |
||||
exist_order.payment_number = payment_number |
||||
exist_order.status = 0 |
||||
exist_order.pay_time = pay_time |
||||
exist_order.save(update_fields=("payment_number", "status", "pay_time", ),) |
||||
except Exception as e: |
||||
pass |
||||
|
||||
return True |
@ -1,45 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<plist version="1.0"><dict> |
||||
<key>items</key> |
||||
<array> |
||||
<dict> |
||||
<key>assets</key> |
||||
<array> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>software-package</string> |
||||
<key>url</key> |
||||
<string><![CDATA[https://ali-fir-pro-binary.jappstore.com/52e972d2749a404028e2ab7edb04d7a7f365184d?auth_key=1583720494-0-0-3dc9d1bf4f093c53f1193614ee5f6d3e]]></string> |
||||
</dict> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>display-image</string> |
||||
<key>needs-shine</key> |
||||
<integer>0</integer> |
||||
<key>url</key> |
||||
<string><![CDATA[https://ali-fir-pro-icon.fir.im/6be40db9e2bb729db1ce0db93b8ebdba50ea8b4d?auth_key=1583720494-0-0-0212635dc7c21a3c02cd2422d0213e99]]></string> |
||||
</dict> |
||||
<dict> |
||||
<key>kind</key> |
||||
<string>full-size-image</string> |
||||
<key>needs-shine</key> |
||||
<true/> |
||||
<key>url</key> |
||||
<string><![CDATA[https://ali-fir-pro-icon.fir.im/6be40db9e2bb729db1ce0db93b8ebdba50ea8b4d?auth_key=1583720494-0-0-0212635dc7c21a3c02cd2422d0213e99]]></string> |
||||
</dict> |
||||
</array> |
||||
<key>metadata</key> |
||||
<dict> |
||||
<key>bundle-identifier</key> |
||||
<string>com.fengxin.im.test</string> |
||||
<key>bundle-version</key> |
||||
<string><![CDATA[1.1.7]]></string> |
||||
<key>kind</key> |
||||
<string>software</string> |
||||
<key>title</key> |
||||
<string><![CDATA[亲友圈发发]]></string> |
||||
</dict> |
||||
</dict> |
||||
</array> |
||||
</dict> |
||||
</plist> |
Loading…
Reference in new issue