# coding=utf-8 import logging from django.http import JsonResponse from django.views.generic import View from typing import TYPE_CHECKING import simplejson as json from voluptuous import MultipleInvalid from apilib.utils_sys import MemcachedLock from apps.web.constant import Const, START_DEVICE_STATUS from apps.web.core import ROLE from apps.web.core.exceptions import ServiceException from apps.web.core.services import wrapper_start_device, StartDeviceEngine from apps.web.core.utils import JsonOkResponse, JsonErrorResponse, async_operation_no_catch from apps.web.device.models import DeviceType from apps.web.exceptions import UnifiedConsumeOrderError from apps.web.user.models import ConsumeRecord, UserBalanceLog, Card, CardBalanceLog from apps.web.user.utils import get_consume_order from apps.web.user.utils2 import UnifiedConsumeOrderManager, ConsumeOrderStateEngine, get_paginate from apps.web.user.validator2 import unifiedConsumeOrderSchema, startConsumeOrderSchema from apps.web.utils import error_tolerate, permission_required, get_start_key_status if TYPE_CHECKING: from django.core.handlers.wsgi import WSGIRequest logger = logging.getLogger(__name__) @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"预下单错误,请刷新页面重试")) @permission_required(ROLE.myuser) def unifiedOrder(request): # type:(WSGIRequest) -> JsonResponse """ 创建消费订单 """ payload = json.loads(request.body) try: data = unifiedConsumeOrderSchema(payload) except MultipleInvalid as me: return JsonErrorResponse(description=u"启动参数校验异常【】".format(me.path[0])) # 解析支付参数 创建支付环境 try: with UnifiedConsumeOrderManager(**data) as manager: order = manager.buildOrder() except UnifiedConsumeOrderError as ue: return JsonErrorResponse(description=ue.message) ConsumeOrderStateEngine(order).to_wait_confirm() return JsonOkResponse(payload={ "orderNo": order.orderNo, "price": order.price, "isFree": order.isFree, "isPostPaid": order.isPostPaid, "logicalCode": order.logicalCode, "package": order.package.showDict }) @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"获取预下单错误,请刷新页面重试")) @permission_required(ROLE.myuser) def getUnifiedOrder(request): orderNo = request.GET.get("orderNo") order = get_consume_order(orderNo) # type: ConsumeRecord if not order: return JsonErrorResponse(u"获取订单失败,请刷新页面重试") if order.status != order.Status.WAIT_CONF: return JsonErrorResponse(u"订单状态异常,请重新扫码下单") balance = order.user.calc_currency_balance( dealer=order.owner, group=order.group ) return JsonOkResponse(payload={ "orderNo": order.orderNo, "price": order.price, "isFree": order.isFree, "balance": balance, "isPostPaid": order.isPostPaid, "logicalCode": order.logicalCode, "package": order.package.showDict }) @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"启动设备异常,请刷新页面重试")) @permission_required(ROLE.myuser) def startAction(request): """ 启动设备 """ payload = json.loads(request.body) try: data = startConsumeOrderSchema(payload) except MultipleInvalid as me: return JsonErrorResponse(description=u"启动参数校验异常【】".format(me.path[0])) order = get_consume_order(data["orderNo"]) # type: ConsumeRecord if not order: logger.warning('[_start_device ERROR] cannot get order, order = {}'.format(data)) return JsonErrorResponse(description=u"启动参数校验异常【订单查询失败】") # 启动锁,防止用户对同一笔订单重复下单并且支付 lockKey = order.startLockKey logger.info( '[_start_device] user({}) on device(devNo={}, port={}) lockKey={}, order={}'.format( order.openId, order.devNo, order.port, lockKey, order ) ) locker = MemcachedLock(key=lockKey, value='1', expire=180) if not locker.acquire(): logger.error( '[_start_device ERROR] cannot get device lock<{}>, openId={}, devNo={}, port={}'.format(lockKey, order.openId, order.devNo, order.port) ) return JsonErrorResponse(description=u'订单正在运行中,请刷新界面查看订单详情') # 订单的状态锁住 防止用户对同一笔订单重复支付 try: proxy = StartDeviceEngine(order) # type: StartDeviceEngine # 检查订单的支付情况 更多的是余额检验 检验完成之后 根据订单的支付选择 决定是否生成支付信息并添加 paymentInfo = proxy.get_payment_info() paymentInfo and order.update_payment(paymentInfo) except ServiceException as se: ConsumeOrderStateEngine(order).to_failure(se.result["description"]) return JsonResponse(se.result) # 这个地方只负责将设备的启动传递到新的线程 对于设备启动的结果实际上是未知的 release_locker = True try: async_operation_no_catch(wrapper_start_device, locker, proxy) release_locker = False except ServiceException as e: logger.info("[[_start_device ERROR] service exception = {}]".format(e.result)) return JsonResponse(e.result) except Exception as e: logger.exception('[_start_device ERROR](order={}) error={}'.format(order, e)) return JsonResponse({'result': 0, 'description': u'系统开小差了,请您重新试试吧', 'payload': {}}) else: return JsonResponse({'result': 1, 'description': u'等待设备启动', 'payload': { 'outTradeNo': order.orderNo, 'orderType': 'consume', 'adShow': order.owner.ad_show }}) finally: logger.debug('release_locker = {}'.format(release_locker)) release_locker and locker.release() @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"获取服务异常")) @permission_required(ROLE.myuser) def getCurrentUse(request): # type: (WSGIRequest) -> JsonResponse openId = request.user.openId orderId = request.GET.get("orderId") pageIndex = int(request.GET.get("pageIndex", 1)) pageSize = int(request.GET.get("pageSize", 10)) query = ConsumeRecord.objects.filter( openId=openId, status__in=[ ConsumeRecord.Status.RUNNING, ConsumeRecord.Status.END ] ) if orderId: query = query.filter(id=orderId) dataList = list() for order in query.skip((pageIndex-1)*pageSize).limit(pageSize): # type: ConsumeRecord device = order.device data = { 'startTime': order.service.deviceStartTime, 'address': order.address, 'groupName': order.groupName, 'devType': order.devTypeName, 'devTypeCode': order.devTypeCode, 'logicalCode': order.logicalCode, 'status': Const.DEV_WORK_STATUS_WORKING, 'devNo': order.devNo, 'majorDeviceType': device.majorDeviceType, 'port': order.port } # 添加功率曲线 if 'show_PG_to_user' in device.owner.features and device.support_power_graph and order.port: data.update({'showPG': True, 'port': order.port}) # 添加各种按钮 data.update(DeviceType.get_services_button(device['devType']['id'])) # 添加端口的使用详情 curInfo = device.deviceAdapter.get_port_using_detail(order.port, {}) data.update(curInfo) dataList.append(data) return JsonOkResponse(payload={ 'total': pageSize * pageIndex, "dataList": dataList } ) class GetCurrentOrder(View): @staticmethod def _get_response(orderProcessing, succeeded, desc='', record = None): record = record or {} return JsonOkResponse( payload = { 'orderProcessing': orderProcessing, 'succeeded': succeeded, 'desc': desc, 'record': record } ) def _get_not_yet_response(self): return self._get_response(orderProcessing=True, succeeded=None) def _get_stop_polling_unknown(self, desc=None): desc = desc or u"系统异常" return self._get_response(orderProcessing=False, succeeded=False, desc=desc) def _get_polling_finished_failed(self, desc, record=None): return self._get_response(orderProcessing=False, succeeded=False, desc=desc, record=record) def _get_polling_finished_success(self, desc, record): return self._get_response(orderProcessing=False, succeeded=True, desc=desc, record=record) def get(self, request): orderNo = request.GET.get("startKey") exp = request.GET.get("exp", 0) start_key_status = get_start_key_status(orderNo) # 先查找失败的 如果订单状态为失败 直接返回不用访问数据库 if start_key_status: state = start_key_status['state'] if state in [START_DEVICE_STATUS.FAILURE]: return self._get_polling_finished_failed( start_key_status['reason'] ) # 其余的状态 if state not in [START_DEVICE_STATUS.FINISHED]: if exp < 2 * 60 * 1000: return self._get_not_yet_response() order = get_consume_order(orderNo) if not order: return self._get_polling_finished_failed(u'订单查询失败') if order.status == order.Status.WAIT_CONF: return self._get_not_yet_response() # 不需要根据状态进行判断了 直接判断设备有无启动即可 if order.service.deviceStartTime: return self._get_polling_finished_success( desc=u'您已成功启动设备。如果有疑问,请点击右下角"设备无反应"按钮', record={'coins': order.price, 'detailLink': order.detail_link} ) else: return self._get_polling_finished_failed( desc=order.description, record={'coins': order.price, 'detailLink': order.detail_link} ) @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"获取服务异常")) @permission_required(ROLE.myuser) def getUserBalanceCharge(request): user = request.user pageIndex = int(request.GET.get("pageIndex", 1)) pageSize = int(request.GET.get("pageSize", 10)) logs = UserBalanceLog.get_logs(user, pageIndex=pageIndex, pageSize=pageSize) dataList = [_.to_dict() for _ in logs.order_by("-id")] return JsonOkResponse(payload={ "total": get_paginate(dataList, pageSize=pageSize, pageIndex=pageIndex), "dataList": dataList }) @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"获取服务异常")) @permission_required(ROLE.myuser, ROLE.dealer) def getCardBalanceCharge(request): cardId = request.GET.get("cardId") pageIndex = int(request.GET.get("pageIndex", 1)) pageSize = int(request.GET.get("pageSize", 10)) card = Card.objects.filter(id=cardId).first() if not card: return JsonErrorResponse(description=u"获取服务异常 查询卡失败") logs = CardBalanceLog.get_logs(card, pageIndex=pageIndex, pageSize=pageSize) dataList = [_.to_dict() for _ in logs.order_by("-id")] return JsonOkResponse(payload={ "total": get_paginate(dataList, pageSize=pageSize, pageIndex=pageIndex), "dataList": dataList })