diff --git a/fir_client/src/components/FirApps.vue b/fir_client/src/components/FirApps.vue index 37f2a42..ca5f315 100644 --- a/fir_client/src/components/FirApps.vue +++ b/fir_client/src/components/FirApps.vue @@ -27,7 +27,9 @@
- +
@@ -334,7 +336,7 @@
+ @click="show_package_prices">
@@ -472,7 +474,7 @@ + + diff --git a/fir_client/src/restful/index.js b/fir_client/src/restful/index.js index 70321db..1c3fe12 100644 --- a/fir_client/src/restful/index.js +++ b/fir_client/src/restful/index.js @@ -482,4 +482,34 @@ export function iosdevicesudid(callBack, params, load = true) { true, true ); +} + +/**获取充值价格信息 */ +export function get_package_prices(callBack, params, load = true) { + getData( + 'GET', + USERSEVER + '/package_prices', + params, + data => { + callBack(data); + }, + load, + true, + true + ); +} + +/**用户订单 */ +export function my_order(callBack, params, load = true) { + getData( + params.methods, + USERSEVER + '/orders', + params.data, + data => { + callBack(data); + }, + load, + true, + true + ); } \ No newline at end of file diff --git a/fir_client/src/router/index.js b/fir_client/src/router/index.js index e462092..69dcb41 100644 --- a/fir_client/src/router/index.js +++ b/fir_client/src/router/index.js @@ -11,25 +11,6 @@ VueRouter.prototype.push = function push(location) { }; -// import FirIndex from "@/components/FirIndex"; -// import FirApps from "@/components/FirApps"; -// import FirLogin from "@/components/FirLogin"; -// import FirRegist from "@/components/FirRegist"; -// import FirDownload from "@/components/FirDownload"; -// import FirAppBase from "@/components/FirAppBase"; -// import FirAppInfosBase from "@/components/FirAppInfosBase"; -// import FirAppInfostimeline from "@/components/FirAppInfostimeline"; -// import FirAppInfosbaseinfo from "@/components/FirAppInfosbaseinfo"; -// import FirAppInfossecurity from "@/components/FirAppInfossecurity"; -// import FirAppInfosdevices from "@/components/FirAppInfosdevices"; -// import FirAppInfoscombo from "@/components/FirAppInfoscombo"; -// import FirUserProfileBase from "@/components/FirUserProfileBase"; -// import FirUserProfileInfo from "@/components/FirUserProfileInfo"; -// import FirUserProfileChangePwd from "@/components/FirUserProfileChangePwd"; -// import FirUserProfileStorage from "@/components/FirUserProfileStorage"; -// -// import FirSuperSignBase from "@/components/FirSuperSignBase"; - const router = new VueRouter({ mode: 'history', routes: [ @@ -175,6 +156,18 @@ const router = new VueRouter({ } ] }, + { + path: '/orders', + component: () => import("@/components/FirAppBase"), + children: [ + { + path: '', + name: 'FirUserOrders', + component: () => import("@/components/FirUserOrders"), + + } + ] + }, { path: '/register', name: 'FirRegist', diff --git a/fir_ser/api/admin.py b/fir_ser/api/admin.py index df22350..37cdcd1 100644 --- a/fir_ser/api/admin.py +++ b/fir_ser/api/admin.py @@ -14,3 +14,5 @@ admin.site.register(AppIOSDeveloperInfo) admin.site.register(APPSuperSignUsedInfo) admin.site.register(APPToDeveloper) admin.site.register(UDIDsyncDeveloper) +admin.site.register(Price) +admin.site.register(Order) diff --git a/fir_ser/api/models.py b/fir_ser/api/models.py index c1a3548..e3b10ef 100644 --- a/fir_ser/api/models.py +++ b/fir_ser/api/models.py @@ -344,3 +344,53 @@ class DeveloperDevicesID(models.Model): def __str__(self): return "%s-%s-%s-%s" % (self.id, self.app_id, self.developerid, self.udid) + + +######################################## 订单表 ######################################## + +class Order(models.Model): + """订单""" + payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (4, '银联')) + payment_type = models.SmallIntegerField(choices=payment_type_choices) + payment_number = models.CharField(max_length=128, verbose_name="支付第3方订单号", null=True, blank=True) + order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True) # 考虑到订单合并支付的问题 + account = models.ForeignKey("UserInfo", on_delete=models.CASCADE) + actual_amount = models.BigIntegerField(verbose_name="实付金额,单位分") + actual_download_times = models.BigIntegerField(verbose_name="实际购买的数量", default=0) + actual_download_gift_times = models.BigIntegerField(verbose_name="实际赠送的数量", default=0) + status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消')) + status = models.SmallIntegerField(choices=status_choices, verbose_name="状态") + order_type_choices = ((0, '用户下单'), (1, '后台充值'),) + order_type = models.SmallIntegerField(choices=order_type_choices, default=0, verbose_name="订单类型") + pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款时间") + cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="订单取消时间") + created_time = models.DateTimeField(verbose_name="订单创建时间", auto_now_add=True) + description = models.TextField('备注', blank=True, null=True, default='') + + +def __str__(self): + return "%s-%s-%s元" % (self.account, self.order_number, self.actual_amount / 100) + + +class Price(models.Model): + name = models.CharField(max_length=128, unique=True, verbose_name="下载包唯一名称") + title = models.CharField(max_length=128, verbose_name="下载包名称") + description = models.CharField(max_length=128, verbose_name="下载包描述") + price = models.BigIntegerField(null=False, verbose_name="下载包价格,单位分") + package_size = models.BigIntegerField(null=False, verbose_name="下载包次数") + download_count_gift = models.IntegerField(default=0, null=False, verbose_name="赠送下载次数") + is_enable = models.BooleanField(default=True, verbose_name="是否启用该价格") + updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") + + class Meta: + verbose_name = '价格列表' + verbose_name_plural = "价格列表" + unique_together = ('price', 'package_size') + + def save(self, *args, **kwargs): + if Price.objects.filter(is_enable=True).count() > 3: # 最多3个启用的价格表 + raise + super(Price, self).save(*args, **kwargs) + + def __str__(self): + return "%s-%s-%s-%s" % (self.name, self.price, self.package_size, self.download_count_gift) diff --git a/fir_ser/api/urls.py b/fir_ser/api/urls.py index 65efae5..b834710 100644 --- a/fir_ser/api/urls.py +++ b/fir_ser/api/urls.py @@ -23,7 +23,7 @@ from api.views.download import ShortDownloadView from api.views.uploads import AppAnalyseView, UploadView from api.views.storage import StorageView from api.views.receiveudids import IosUDIDView - +from api.views.order import PriceView, OrderView from api.views.supersign import DeveloperView, SuperSignUsedView, AppUDIDUsedView # router=DefaultRouter() @@ -49,5 +49,7 @@ urlpatterns = [ re_path("^supersign/developer$", DeveloperView.as_view()), re_path("^supersign/devices$", SuperSignUsedView.as_view()), re_path("^supersign/udid$", AppUDIDUsedView.as_view()), + re_path("^package_prices$", PriceView.as_view()), + re_path("^orders$", OrderView.as_view()), ] diff --git a/fir_ser/api/utils/serializer.py b/fir_ser/api/utils/serializer.py index 64f97b5..7f8da25 100644 --- a/fir_ser/api/utils/serializer.py +++ b/fir_ser/api/utils/serializer.py @@ -320,3 +320,15 @@ class DeviceUDIDSerializer(serializers.ModelSerializer): def get_bundle_name(self, obj): return obj.app_id.name + + +class PriceSerializer(serializers.ModelSerializer): + class Meta: + model = models.Price + exclude = ["id"] + + +class OrdersSerializer(serializers.ModelSerializer): + class Meta: + model = models.Order + exclude = ["id"] diff --git a/fir_ser/api/utils/storage/caches.py b/fir_ser/api/utils/storage/caches.py index 4215ab7..2965927 100644 --- a/fir_ser/api/utils/storage/caches.py +++ b/fir_ser/api/utils/storage/caches.py @@ -350,5 +350,18 @@ def consume_user_download_times(user_id, app_id, amount=1): return True +def add_user_download_times(user_id, download_times=0): + with cache.lock("%s_%s" % ('consume_user_download_times', user_id)): + try: + UserInfo.objects.filter(pk=user_id).update(download_times=F('download_times') + download_times) + enable_user_download_times_flag(user_id) + for app_obj in Apps.objects.filter(pk=user_id): + del_cache_response_by_short(app_obj.app_id) + return True + except Exception as e: + logger.error("%s download_times less then 0. Exception:%s" % (user_id, e)) + return False + + def check_user_has_all_download_times(user_id): return get_user_free_download_times(user_id) > 0 or check_user_can_download(user_id) diff --git a/fir_ser/api/utils/utils.py b/fir_ser/api/utils/utils.py index 907cedd..f0d15e4 100644 --- a/fir_ser/api/utils/utils.py +++ b/fir_ser/api/utils/utils.py @@ -3,7 +3,7 @@ # project: 5月 # author: liuyu # date: 2020/5/7 -import os, re, json, requests +import os, re, json, requests, datetime, random from fir_ser.settings import SUPER_SIGN_ROOT, SERVER_DOMAIN, CAPTCHA_LENGTH, MEDIA_ROOT from api.models import APPSuperSignUsedInfo, APPToDeveloper, \ UDIDsyncDeveloper, UserInfo, AppReleaseInfo, AppScreenShot @@ -335,3 +335,11 @@ def clean_storage_data(user_obj, storage_obj=None): for apptodev_obj in APPToDeveloper.objects.filter(app_id=app_release_obj.app_id).all(): storage_obj.delete_file(apptodev_obj.binary_file, app_release_obj.release_type) return True + + +def get_order_num(order_type=1): + now = datetime.datetime.now() + date_str = "{0}{1}{2}{3}{4}{5}{6}".format(order_type, now.year, now.month, now.day, now.hour, now.minute, + now.second) + return date_str + str(random.randint(1000, 9999)) + str(random.randint(1000, 9999)) + str( + random.randint(1000, 9999)) diff --git a/fir_ser/api/views/order.py b/fir_ser/api/views/order.py new file mode 100644 index 0000000..f42d782 --- /dev/null +++ b/fir_ser/api/views/order.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +# project: 3月 +# author: NinEveN +# date: 2021/3/29 + +from rest_framework.views import APIView +from api.utils.response import BaseResponse +from api.utils.auth import ExpiringTokenAuthentication +from rest_framework.response import Response +from api.models import UserInfo, Price, Order +from api.utils.serializer import PriceSerializer, OrdersSerializer +from rest_framework.pagination import PageNumberPagination +from api.utils.utils import get_order_num +from api.utils.storage.caches import add_user_download_times +import logging +from django.utils import timezone + +logger = logging.getLogger(__name__) + + +class PageNumber(PageNumberPagination): + page_size = 10 # 每页显示多少条 + page_size_query_param = 'size' # URL中每页显示条数的参数 + page_query_param = 'page' # URL中页码的参数 + max_page_size = None # 最大页码数限制 + + +class OrderView(APIView): + authentication_classes = [ExpiringTokenAuthentication, ] + + def get(self, request): + res = BaseResponse() + order_id = request.query_params.get("order_id", None) + order_obj_lists = Order.objects.filter(account=request.user) + if order_id: + order_obj_lists = order_obj_lists.filter(order_number=order_id) + page_obj = PageNumber() + order_info_serializer = page_obj.paginate_queryset(queryset=order_obj_lists.order_by("-created_time"), + request=request, + view=self) + order_info = OrdersSerializer(order_info_serializer, many=True, ) + res.data = order_info.data + res.count = order_obj_lists.count() + + res.payment_type_choices = [] + choices_list = list(Order.payment_type_choices) + for auth_t in choices_list: + res.payment_type_choices.append({'id': auth_t[0], 'name': auth_t[1]}) + + res.status_choices = [] + choices_list = list(Order.status_choices) + for auth_t in choices_list: + res.status_choices.append({'id': auth_t[0], 'name': auth_t[1]}) + + res.order_type_choices = [] + choices_list = list(Order.order_type_choices) + for auth_t in choices_list: + res.order_type_choices.append({'id': auth_t[0], 'name': auth_t[1]}) + + return Response(res.dict) + + def post(self, request): + res = BaseResponse() + price_id = request.data.get("price_id", None) + if price_id: + price_obj = Price.objects.filter(name=price_id).first() + if price_obj: + try: + Order.objects.create(payment_type=0, order_number=get_order_num(), + account=request.user, status=1, order_type=0, actual_amount=price_obj.price, + actual_download_times=price_obj.package_size, + actual_download_gift_times=price_obj.download_count_gift) + return Response(res.dict) + except Exception as e: + logger.error("%s 订单 %s 保存失败 Exception:%s" % (request.user, price_id, e)) + res.code = 1003 + res.msg = "订单保存失败" + else: + logger.error("%s 价格 %s 获取失败" % (request.user, price_id)) + res.code = 1002 + res.msg = "价格获取失败" + else: + res.code = 1001 + res.msg = "错误的价格" + return Response(res.dict) + + def put(self, request): + res = BaseResponse() + order_number = request.data.get("order_number", None) + if order_number: + order_obj = Order.objects.filter(account=request.user, order_number=order_number, status=1).first() + if order_obj: + download_times = order_obj.actual_download_times + order_obj.actual_download_gift_times + try: + order_obj.status = 0 + now = timezone.now() + if not timezone.is_naive(now): + now = timezone.make_naive(now, timezone.utc) + order_obj.pay_time = now + n_download_times = UserInfo.objects.filter(pk=request.user.id).values('download_times').first() + order_obj.description = "充值成功,充值下载次数 %s ,现总共可用次数 %s" % ( + download_times, n_download_times.get("download_times", 0)) + order_obj.save() + add_user_download_times(request.user.id, download_times) + except Exception as e: + logger.error("%s 订单 %s 更新失败 Exception:%s" % (request.user, order_number, e)) + res.code = 1003 + return Response(res.dict) + else: + logger.error("%s 订单 %s 获取失败" % (request.user, order_number)) + res.code = 1003 + res.msg = "订单获取失败,或订单已经支付" + else: + res.code = 1001 + res.msg = "订单有误" + return Response(res.dict) + + +class PriceView(APIView): + authentication_classes = [ExpiringTokenAuthentication, ] + + def get(self, request): + res = BaseResponse() + price_obj_lists = Price.objects.filter(is_enable=True).all().order_by("updated_time").order_by("price") + res.data = PriceSerializer(price_obj_lists, many=True).data + return Response(res.dict) + + def delete(self, request, price_id): + res = BaseResponse() + return Response(res.dict) + + def put(self, request, price_id): + res = BaseResponse() + return Response(res.dict) diff --git a/fir_ser/api/views/supersign.py b/fir_ser/api/views/supersign.py index 44e2c60..6c301c6 100644 --- a/fir_ser/api/views/supersign.py +++ b/fir_ser/api/views/supersign.py @@ -19,7 +19,7 @@ import logging logger = logging.getLogger(__file__) -class AppsPageNumber(PageNumberPagination): +class PageNumber(PageNumberPagination): page_size = 10 # 每页显示多少条 page_size_query_param = 'size' # URL中每页显示条数的参数 page_query_param = 'page' # URL中页码的参数 @@ -42,7 +42,7 @@ class DeveloperView(APIView): developer_obj2 = developer_obj.filter(issuer_id=appid) developer_obj = developer_obj1 | developer_obj2 - page_obj = AppsPageNumber() + page_obj = PageNumber() app_page_serializer = page_obj.paginate_queryset(queryset=developer_obj.order_by("-updated_time"), request=request, view=self) @@ -238,7 +238,7 @@ class SuperSignUsedView(APIView): if bundleid: SuperSignUsed_obj = SuperSignUsed_obj.filter(app_id__bundle_id=bundleid) - page_obj = AppsPageNumber() + page_obj = PageNumber() app_page_serializer = page_obj.paginate_queryset(queryset=SuperSignUsed_obj.order_by("-created_time"), request=request, view=self) @@ -263,7 +263,7 @@ class AppUDIDUsedView(APIView): if bundleid: AppUDID_obj = AppUDID_obj.filter(app_id__bundle_id=bundleid) - page_obj = AppsPageNumber() + page_obj = PageNumber() app_page_serializer = page_obj.paginate_queryset(queryset=AppUDID_obj.order_by("-created_time"), request=request, view=self)