views2.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. # coding=utf-8
  2. import logging
  3. from django.http import JsonResponse
  4. from django.views.generic import View
  5. from typing import TYPE_CHECKING
  6. import simplejson as json
  7. from voluptuous import MultipleInvalid
  8. from apilib.utils_sys import MemcachedLock
  9. from apps.web.constant import Const, START_DEVICE_STATUS
  10. from apps.web.core import ROLE
  11. from apps.web.core.exceptions import ServiceException
  12. from apps.web.core.services import wrapper_start_device, StartDeviceEngine
  13. from apps.web.core.utils import JsonOkResponse, JsonErrorResponse, async_operation_no_catch
  14. from apps.web.device.models import DeviceType
  15. from apps.web.exceptions import UnifiedConsumeOrderError
  16. from apps.web.user.models import ConsumeRecord, UserBalanceLog, Card, CardBalanceLog
  17. from apps.web.user.utils import get_consume_order
  18. from apps.web.user.utils2 import UnifiedConsumeOrderManager, ConsumeOrderStateEngine, get_paginate
  19. from apps.web.user.validator2 import unifiedConsumeOrderSchema, startConsumeOrderSchema
  20. from apps.web.utils import error_tolerate, permission_required, get_start_key_status
  21. if TYPE_CHECKING:
  22. from django.core.handlers.wsgi import WSGIRequest
  23. logger = logging.getLogger(__name__)
  24. @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"预下单错误,请刷新页面重试"))
  25. @permission_required(ROLE.myuser)
  26. def unifiedOrder(request): # type:(WSGIRequest) -> JsonResponse
  27. """
  28. 创建消费订单
  29. """
  30. payload = json.loads(request.body)
  31. try:
  32. data = unifiedConsumeOrderSchema(payload)
  33. except MultipleInvalid as me:
  34. return JsonErrorResponse(description=u"启动参数校验异常【】".format(me.path[0]))
  35. # 解析支付参数 创建支付环境
  36. try:
  37. with UnifiedConsumeOrderManager(**data) as manager:
  38. order = manager.buildOrder()
  39. except UnifiedConsumeOrderError as ue:
  40. return JsonErrorResponse(description=ue.message)
  41. ConsumeOrderStateEngine(order).to_wait_confirm()
  42. return JsonOkResponse(payload={
  43. "orderNo": order.orderNo,
  44. "price": order.price,
  45. "isFree": order.isFree,
  46. "isPostPaid": order.isPostPaid,
  47. "logicalCode": order.logicalCode,
  48. "package": order.package.showDict
  49. })
  50. @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"获取预下单错误,请刷新页面重试"))
  51. @permission_required(ROLE.myuser)
  52. def getUnifiedOrder(request):
  53. orderNo = request.GET.get("orderNo")
  54. order = get_consume_order(orderNo) # type: ConsumeRecord
  55. if not order:
  56. return JsonErrorResponse(u"获取订单失败,请刷新页面重试")
  57. if order.status != order.Status.WAIT_CONF:
  58. return JsonErrorResponse(u"订单状态异常,请重新扫码下单")
  59. balance = order.user.calc_currency_balance(
  60. dealer=order.owner,
  61. group=order.group
  62. )
  63. return JsonOkResponse(payload={
  64. "orderNo": order.orderNo,
  65. "price": order.price,
  66. "isFree": order.isFree,
  67. "balance": balance,
  68. "isPostPaid": order.isPostPaid,
  69. "logicalCode": order.logicalCode,
  70. "package": order.package.showDict
  71. })
  72. @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"启动设备异常,请刷新页面重试"))
  73. @permission_required(ROLE.myuser)
  74. def startAction(request):
  75. """
  76. 启动设备
  77. """
  78. payload = json.loads(request.body)
  79. try:
  80. data = startConsumeOrderSchema(payload)
  81. except MultipleInvalid as me:
  82. return JsonErrorResponse(description=u"启动参数校验异常【】".format(me.path[0]))
  83. order = get_consume_order(data["orderNo"]) # type: ConsumeRecord
  84. if not order:
  85. logger.warning('[_start_device ERROR] cannot get order, order = {}'.format(data))
  86. return JsonErrorResponse(description=u"启动参数校验异常【订单查询失败】")
  87. # 启动锁,防止用户对同一笔订单重复下单并且支付
  88. lockKey = order.startLockKey
  89. logger.info(
  90. '[_start_device] user({}) on device(devNo={}, port={}) lockKey={}, order={}'.format(
  91. order.openId, order.devNo, order.port, lockKey, order
  92. )
  93. )
  94. locker = MemcachedLock(key=lockKey, value='1', expire=180)
  95. if not locker.acquire():
  96. logger.error(
  97. '[_start_device ERROR] cannot get device lock<{}>, openId={}, devNo={}, port={}'.format(lockKey, order.openId, order.devNo, order.port)
  98. )
  99. return JsonErrorResponse(description=u'订单正在运行中,请刷新界面查看订单详情')
  100. # 订单的状态锁住 防止用户对同一笔订单重复支付
  101. try:
  102. proxy = StartDeviceEngine(order) # type: StartDeviceEngine
  103. # 检查订单的支付情况 更多的是余额检验 检验完成之后 根据订单的支付选择 决定是否生成支付信息并添加
  104. paymentInfo = proxy.get_payment_info()
  105. paymentInfo and order.update_payment(paymentInfo)
  106. except ServiceException as se:
  107. ConsumeOrderStateEngine(order).to_failure(se.result["description"])
  108. return JsonResponse(se.result)
  109. # 这个地方只负责将设备的启动传递到新的线程 对于设备启动的结果实际上是未知的
  110. release_locker = True
  111. try:
  112. async_operation_no_catch(wrapper_start_device, locker, proxy)
  113. release_locker = False
  114. except ServiceException as e:
  115. logger.info("[[_start_device ERROR] service exception = {}]".format(e.result))
  116. return JsonResponse(e.result)
  117. except Exception as e:
  118. logger.exception('[_start_device ERROR](order={}) error={}'.format(order, e))
  119. return JsonResponse({'result': 0, 'description': u'系统开小差了,请您重新试试吧', 'payload': {}})
  120. else:
  121. return JsonResponse({'result': 1, 'description': u'等待设备启动', 'payload': {
  122. 'outTradeNo': order.orderNo, 'orderType': 'consume', 'adShow': order.owner.ad_show
  123. }})
  124. finally:
  125. logger.debug('release_locker = {}'.format(release_locker))
  126. release_locker and locker.release()
  127. @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"获取服务异常"))
  128. @permission_required(ROLE.myuser)
  129. def getCurrentUse(request): # type: (WSGIRequest) -> JsonResponse
  130. openId = request.user.openId
  131. orderId = request.GET.get("orderId")
  132. pageIndex = int(request.GET.get("pageIndex", 1))
  133. pageSize = int(request.GET.get("pageSize", 10))
  134. query = ConsumeRecord.objects.filter(
  135. openId=openId,
  136. status__in=[
  137. ConsumeRecord.Status.RUNNING, ConsumeRecord.Status.END
  138. ]
  139. )
  140. if orderId:
  141. query = query.filter(id=orderId)
  142. dataList = list()
  143. for order in query.skip((pageIndex-1)*pageSize).limit(pageSize): # type: ConsumeRecord
  144. device = order.device
  145. data = {
  146. 'startTime': order.service.deviceStartTime,
  147. 'address': order.address,
  148. 'groupName': order.groupName,
  149. 'devType': order.devTypeName,
  150. 'devTypeCode': order.devTypeCode,
  151. 'logicalCode': order.logicalCode,
  152. 'status': Const.DEV_WORK_STATUS_WORKING,
  153. 'devNo': order.devNo,
  154. 'majorDeviceType': device.majorDeviceType,
  155. 'port': order.port
  156. }
  157. # 添加功率曲线
  158. if 'show_PG_to_user' in device.owner.features and device.support_power_graph and order.port:
  159. data.update({'showPG': True, 'port': order.port})
  160. # 添加各种按钮
  161. data.update(DeviceType.get_services_button(device['devType']['id']))
  162. # 添加端口的使用详情
  163. curInfo = device.deviceAdapter.get_port_using_detail(order.port, {})
  164. data.update(curInfo)
  165. dataList.append(data)
  166. return JsonOkResponse(payload={
  167. 'total': pageSize * pageIndex,
  168. "dataList": dataList
  169. }
  170. )
  171. class GetCurrentOrder(View):
  172. @staticmethod
  173. def _get_response(orderProcessing, succeeded, desc='', record = None):
  174. record = record or {}
  175. return JsonOkResponse(
  176. payload = {
  177. 'orderProcessing': orderProcessing,
  178. 'succeeded': succeeded,
  179. 'desc': desc,
  180. 'record': record
  181. }
  182. )
  183. def _get_not_yet_response(self):
  184. return self._get_response(orderProcessing=True, succeeded=None)
  185. def _get_stop_polling_unknown(self, desc=None):
  186. desc = desc or u"系统异常"
  187. return self._get_response(orderProcessing=False, succeeded=False, desc=desc)
  188. def _get_polling_finished_failed(self, desc, record=None):
  189. return self._get_response(orderProcessing=False, succeeded=False, desc=desc, record=record)
  190. def _get_polling_finished_success(self, desc, record):
  191. return self._get_response(orderProcessing=False, succeeded=True, desc=desc, record=record)
  192. def get(self, request):
  193. orderNo = request.GET.get("startKey")
  194. exp = request.GET.get("exp", 0)
  195. start_key_status = get_start_key_status(orderNo)
  196. # 先查找失败的 如果订单状态为失败 直接返回不用访问数据库
  197. if start_key_status:
  198. state = start_key_status['state']
  199. if state in [START_DEVICE_STATUS.FAILURE]:
  200. return self._get_polling_finished_failed(
  201. start_key_status['reason']
  202. )
  203. # 其余的状态
  204. if state not in [START_DEVICE_STATUS.FINISHED]:
  205. if exp < 2 * 60 * 1000:
  206. return self._get_not_yet_response()
  207. order = get_consume_order(orderNo)
  208. if not order:
  209. return self._get_polling_finished_failed(u'订单查询失败')
  210. if order.status == order.Status.WAIT_CONF:
  211. return self._get_not_yet_response()
  212. # 不需要根据状态进行判断了 直接判断设备有无启动即可
  213. if order.service.deviceStartTime:
  214. return self._get_polling_finished_success(
  215. desc=u'您已成功启动设备。如果有疑问,请点击右下角"设备无反应"按钮',
  216. record={'coins': order.price, 'detailLink': order.detail_link}
  217. )
  218. else:
  219. return self._get_polling_finished_failed(
  220. desc=order.description,
  221. record={'coins': order.price, 'detailLink': order.detail_link}
  222. )
  223. @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"获取服务异常"))
  224. @permission_required(ROLE.myuser)
  225. def getUserBalanceCharge(request):
  226. user = request.user
  227. pageIndex = int(request.GET.get("pageIndex", 1))
  228. pageSize = int(request.GET.get("pageSize", 10))
  229. logs = UserBalanceLog.get_logs(user, pageIndex=pageIndex, pageSize=pageSize)
  230. dataList = [_.to_dict() for _ in logs.order_by("-id")]
  231. return JsonOkResponse(payload={
  232. "total": get_paginate(dataList, pageSize=pageSize, pageIndex=pageIndex),
  233. "dataList": dataList
  234. })
  235. @error_tolerate(logger=logger, nil=JsonErrorResponse(description=u"获取服务异常"))
  236. @permission_required(ROLE.myuser, ROLE.dealer)
  237. def getCardBalanceCharge(request):
  238. cardId = request.GET.get("cardId")
  239. pageIndex = int(request.GET.get("pageIndex", 1))
  240. pageSize = int(request.GET.get("pageSize", 10))
  241. card = Card.objects.filter(id=cardId).first()
  242. if not card:
  243. return JsonErrorResponse(description=u"获取服务异常 查询卡失败")
  244. logs = CardBalanceLog.get_logs(card, pageIndex=pageIndex, pageSize=pageSize)
  245. dataList = [_.to_dict() for _ in logs.order_by("-id")]
  246. return JsonOkResponse(payload={
  247. "total": get_paginate(dataList, pageSize=pageSize, pageIndex=pageIndex),
  248. "dataList": dataList
  249. })