AgentDeviceController.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @File : AgentDeviceController.py
  4. @Time : 2024/3/8 13:55
  5. @Author : stephen
  6. @Email : zhangdongming@asj6.wecom.work
  7. @Software: PyCharm
  8. """
  9. import os
  10. import csv
  11. import time
  12. from datetime import datetime
  13. import calendar
  14. from typing import Dict, Any, Tuple, List, Optional
  15. from dateutil.relativedelta import relativedelta
  16. from collections import defaultdict
  17. from decimal import Decimal
  18. import traceback
  19. import threading
  20. from django.db import transaction
  21. from django.db.models import Q
  22. from django.http import QueryDict
  23. from django.views import View
  24. from django.core.paginator import Paginator
  25. from AgentModel.models import AgentCustomerInfo, AgentDeviceOrder, AgentDevice, AgentCloudServicePackage, CustomUIDPool, \
  26. DeviceCustomUID
  27. from Model.models import DeviceTypeModel
  28. from Object.ResponseObject import ResponseObject
  29. from Ansjer.config import LOGGER
  30. from Object.TokenObject import TokenObject
  31. class AgentDeviceView(View):
  32. def get(self, request, *args, **kwargs):
  33. request.encoding = 'utf-8'
  34. operation = kwargs.get('operation')
  35. return self.validation(request.GET, request, operation)
  36. def post(self, request, *args, **kwargs):
  37. request.encoding = 'utf-8'
  38. operation = kwargs.get('operation')
  39. return self.validation(request.POST, request, operation)
  40. def delete(self, request, *args, **kwargs):
  41. request.encoding = 'utf-8'
  42. operation = kwargs.get('operation')
  43. delete = QueryDict(request.body)
  44. if not delete:
  45. delete = request.GET
  46. return self.validation(delete, request, operation)
  47. def put(self, request, *args, **kwargs):
  48. request.encoding = 'utf-8'
  49. operation = kwargs.get('operation')
  50. put = QueryDict(request.body)
  51. return self.validation(put, request, operation)
  52. def validation(self, request_dict, request, operation):
  53. language = request_dict.get('language', 'en')
  54. response = ResponseObject(language, 'pc')
  55. # 订单结算界面
  56. if operation == 'XXXXX':
  57. pass
  58. else:
  59. tko = TokenObject(
  60. request.META.get('HTTP_AUTHORIZATION'),
  61. returntpye='pc')
  62. if tko.code != 0:
  63. return response.json(tko.code)
  64. response.lang = tko.lang
  65. userID = tko.userID
  66. if operation == 'getAgentDevice':
  67. return self.get_agent_device(userID, request_dict, response)
  68. elif operation == 'getAgentDeviceOrder':
  69. return self.get_agent_device_order(userID, request_dict, response)
  70. elif operation == 'batchBandDevice':
  71. return self.batch_band_device(userID, request, request_dict, response)
  72. elif operation == 'customUidBind':
  73. return self.custom_uid_bindings(request_dict, response)
  74. else:
  75. return response.json(444, 'operation')
  76. def get_agent_device(self, userID: str, request_dict: Dict[str, Any],
  77. response: ResponseObject) -> Optional[Dict[str, Any]]:
  78. """
  79. 查询设备明细
  80. Args:
  81. userID (str): 用户ID
  82. request_dict (Dict[str, Any]): 请求参数,包含以下字段:
  83. - ac_id (int): 代理商ID
  84. - device_name (str): 设备名称
  85. - status (str): 设备状态
  86. - serial_number (str): 设备序列号
  87. response (ResponseObject): 响应对象
  88. Returns:
  89. Optional[Dict[str, Any]]: 返回设备明细列表及分页信息,可能为None
  90. Raises:
  91. ValueError: 当参数无效时抛出
  92. RuntimeError: 当操作失败时抛出
  93. Example:
  94. get_agent_device("user123", {"ac_id": 1, "device_name": "test"}, response)
  95. {'list': [...], 'total': 10, 'page': 1, 'page_size': 10, 'num_pages': 1}
  96. """
  97. device_name = request_dict.get('device_name', None)
  98. status = request_dict.get('status', None)
  99. serial_number = request_dict.get('serial_number', None)
  100. company_id = int(request_dict.get('ac_id', 0)) # 默认查询所有公司
  101. page = int(request_dict.get('page', 1)) # 默认为第一页
  102. page_size = int(request_dict.get('page_size', 10)) # 默认每页10条记录
  103. try:
  104. agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
  105. if agent_customer_info is None:
  106. agent_device_qs = AgentDevice.objects.order_by('ac_id', '-created_time')
  107. else:
  108. ac_id = agent_customer_info.id
  109. agent_device_qs = AgentDevice.objects.filter(ac_id=ac_id).order_by('ac_id', '-created_time')
  110. if company_id > 0:
  111. agent_device_qs = agent_device_qs.filter(ac_id=company_id)
  112. if device_name:
  113. # 根据device_name查询对应的type值
  114. device_types = list(DeviceTypeModel.objects.filter(name=device_name).values_list('type', flat=True))
  115. agent_device_qs = agent_device_qs.filter(type__in=device_types)
  116. if status:
  117. agent_device_qs = agent_device_qs.filter(status=status)
  118. if serial_number:
  119. agent_device_qs = agent_device_qs.filter(serial_number=serial_number)
  120. # 应用分页
  121. paginator = Paginator(agent_device_qs, page_size)
  122. current_page = paginator.get_page(page)
  123. # 构造返回列表
  124. device_list = []
  125. for device in current_page:
  126. device_type = DeviceTypeModel.objects.filter(type=device.type).first()
  127. device_name = device_type.name if device_type else device.type
  128. agent_customer_info = AgentCustomerInfo.objects.filter(id=device.ac_id).first()
  129. company_name = agent_customer_info.company_name if agent_customer_info else device.ac_id
  130. device_list.append({
  131. 'id': device.id,
  132. 'ac_id': device.ac_id,
  133. 'company_name': company_name,
  134. 'status': device.status,
  135. 'serial_number': device.serial_number,
  136. 'device_name': device_name,
  137. 'at_time': device.at_time,
  138. })
  139. # 包含分页信息的响应
  140. response_data = {
  141. 'list': device_list,
  142. 'total': paginator.count,
  143. 'page': current_page.number,
  144. 'page_size': page_size,
  145. 'num_pages': paginator.num_pages,
  146. }
  147. return response.json(0, response_data)
  148. except Exception as e:
  149. error_msg = f'查询设备明细 - 用户: {userID}, 错误: {str(e)}'
  150. error_line = e.__traceback__.tb_lineno
  151. LOGGER.error(f'{error_msg} 行号: {error_line}')
  152. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  153. def calculate_profit_or_revenue(self, agent_device_orders, package_details, time_unit, metric_type, start_time,
  154. end_time):
  155. """
  156. 计算利润或者营业额
  157. @param agent_device_orders: 代理设备订单
  158. @param package_details: 代理套餐详情
  159. @param time_unit: 时间单位
  160. @param metric_type: 利润或者营业额
  161. @param start_time: 开始时间
  162. @param end_time: 结束时间
  163. @return:
  164. """
  165. summary = defaultdict(lambda: {"云存": Decimal('0.00'), "4G": Decimal('0.00'), "all": Decimal('0.00')})
  166. time_format = {
  167. "month": "%Y-%m",
  168. "year": "%Y",
  169. "quarter": lambda x: f"{x.year}年{(x.month - 1) // 3 + 1}季度"
  170. }
  171. for order in agent_device_orders:
  172. package = package_details.get(order.csp_id)
  173. if not package:
  174. continue
  175. # 根据利润类型计算利润或者直接使用营业额
  176. if metric_type == 1: # 利润
  177. profit = order.profit
  178. else: # 营业额
  179. profit = order.profit_amount
  180. # 区分云服务 + 4G套餐并加入 summary
  181. service_type = "云存" if package.type == 1 else "4G"
  182. time_key = datetime.fromtimestamp(order.created_time).strftime(
  183. time_format[time_unit]) if time_unit != "quarter" else time_format[time_unit](
  184. datetime.fromtimestamp(order.created_time))
  185. summary[time_key][service_type] += profit
  186. summary[time_key]["all"] += profit
  187. # 补全时间段内所有可能的时间单位
  188. current_time = start_time
  189. while current_time < end_time:
  190. time_key = current_time.strftime(time_format[time_unit]) if time_unit != "quarter" else time_format[
  191. time_unit](current_time)
  192. if time_key not in summary:
  193. summary[time_key] = {"云存": Decimal('0.00'), "4G": Decimal('0.00'), "all": Decimal('0.00')}
  194. current_time += relativedelta(months=1) if time_unit == "month" else relativedelta(
  195. years=1) if time_unit == "year" else relativedelta(months=3)
  196. return [{"time": time, **data} for time, data in sorted(summary.items())]
  197. def get_agent_device_order(self, userID, request_dict, response):
  198. """
  199. 查询设备订单明细
  200. @param userID: userID
  201. @param request_dict: 请求参数
  202. @param request_dict startTime: 开始时间
  203. @param request_dict endTime: 结束时间
  204. @param request_dict timeUnit: 时间单位
  205. @param request_dict metric_type: 利润或者营业额
  206. @param response: 响应对象
  207. @return:
  208. """
  209. try:
  210. startTime = int(request_dict.get('startTime', 1704038400))
  211. endTime = int(request_dict.get('endTime', 1732982400))
  212. timeUnit = request_dict.get('timeUnit', 'month')
  213. metric_type = int(request_dict.get('metric_type', 0))
  214. # endTime变成每个月最后一天
  215. end_datetime = datetime.fromtimestamp(endTime)
  216. month_str = end_datetime.strftime('%Y-%m')
  217. year, month = int(month_str.split('-')[0]), int(month_str.split('-')[1])
  218. end = calendar.monthrange(year, month)[1]
  219. end_timestamp = datetime(year, month, end, 23, 59, 59).timestamp()
  220. endTime = int(end_timestamp)
  221. agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
  222. if not agent_customer_info:
  223. return response.json(104, 'Agent customer not found')
  224. agent_device_orders = AgentDeviceOrder.objects.filter(
  225. ac_id=agent_customer_info.id, created_time__gte=startTime, created_time__lte=endTime, status__in=[1, 2]
  226. )
  227. # 获取代理套餐包id
  228. package_ids = agent_device_orders.values_list('csp_id', flat=True).distinct()
  229. package_details = {pkg.id: pkg for pkg in AgentCloudServicePackage.objects.filter(id__in=package_ids)}
  230. start_time = datetime.fromtimestamp(startTime)
  231. end_time = datetime.fromtimestamp(endTime)
  232. result = self.calculate_profit_or_revenue(agent_device_orders, package_details, timeUnit, metric_type,
  233. start_time, end_time)
  234. total_4G = Decimal('0.00')
  235. total_cloud = Decimal('0.00')
  236. # 遍历result列表来累加4G和云存的值
  237. for item in result:
  238. total_4G = item['4G'] + total_4G
  239. total_cloud = item['云存'] + total_cloud
  240. response_data = {
  241. "list": result,
  242. "total_4G": total_4G, # 4G的总和
  243. "total_云存": total_cloud, # 云存的总和
  244. }
  245. return response.json(0, response_data)
  246. except Exception as e:
  247. error_msg = f"error_line:{traceback.format_exc()}, error_msg:{str(e)}"
  248. return response.json(500, error_msg)
  249. def agent_devices_from_csv(self, ac_id, device_type, userID, file_path):
  250. """
  251. 异步批量绑定设备
  252. """
  253. try:
  254. with open(file_path, 'r') as file:
  255. reader = csv.DictReader(file)
  256. devices_to_create = []
  257. # 先收集所有CSV中的序列号
  258. csv_serial_numbers = [row.get('serial_number') for row in reader]
  259. # 去重
  260. unique_serial_numbers = set(csv_serial_numbers)
  261. existing = set(AgentDevice.objects.filter(
  262. serial_number__in=unique_serial_numbers
  263. ).values_list('serial_number', flat=True))
  264. for row in unique_serial_numbers:
  265. serial_number = row
  266. if serial_number not in existing:
  267. device = AgentDevice(
  268. ac_id=ac_id,
  269. serial_number=serial_number,
  270. type=device_type,
  271. status=0,
  272. created_time=int(time.time()),
  273. created_by=userID,
  274. updated_time=int(time.time()),
  275. updated_by=userID
  276. )
  277. devices_to_create.append(device)
  278. # 使用Django的bulk_create来批量创建对象
  279. if devices_to_create:
  280. with transaction.atomic():
  281. AgentDevice.objects.bulk_create(devices_to_create, batch_size=200)
  282. # 删除文件
  283. os.remove(file_path)
  284. except Exception as e:
  285. LOGGER.info('errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  286. def batch_band_device(self, userID, request, request_dict, response):
  287. """
  288. 批量绑定设备
  289. @param ac_id: ac_id 代理商id
  290. @param userID: userID
  291. @param csv_file_path: 文件路径
  292. @param response: 响应对象
  293. @return:
  294. """
  295. ac_id = request_dict.get('ac_id', None)
  296. device_name = request_dict.get('device_name', None)
  297. csv_file = request.FILES['file']
  298. upload_dir = os.path.join('static', 'uploaded_files')
  299. if not all([ac_id, device_name]):
  300. return response.json(444)
  301. try:
  302. device_type_dict = DeviceTypeModel.objects.filter(name=device_name).values('type').first()
  303. device_type = device_type_dict['type']
  304. if not os.path.exists(upload_dir):
  305. os.makedirs(upload_dir)
  306. file_path = os.path.join(upload_dir, csv_file.name)
  307. with open(file_path, 'wb+') as destination:
  308. for chunk in csv_file.chunks():
  309. destination.write(chunk)
  310. # 创建并启动线程来异步执行任务
  311. thread = threading.Thread(target=self.agent_devices_from_csv, args=(ac_id, device_type, userID, file_path))
  312. thread.start()
  313. return response.json(0)
  314. except Exception as e:
  315. print(e)
  316. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  317. @classmethod
  318. def device_binding_or_unbinding(cls, serial_number, bind_type):
  319. """
  320. 设备绑定或解绑
  321. @param serial_number: 设备序列号
  322. @param bind_type: 绑定类型 1:绑定 2:解绑
  323. @return: 无返回值
  324. """
  325. try:
  326. # 获取设备信息
  327. device_info = AgentDevice.objects.filter(serial_number=serial_number)
  328. if not device_info.exists():
  329. return
  330. LOGGER.info('同步更新设备激活状态serial:{},type:{}'.format(serial_number, bind_type))
  331. n_time = int(time.time())
  332. if bind_type == 1:
  333. # 绑定设备
  334. device_info.update(status=1, updated_time=n_time, at_time=n_time)
  335. elif bind_type == 2:
  336. # 解绑设备
  337. device_info.update(status=0, updated_time=n_time)
  338. except Exception as e:
  339. LOGGER.error('*****AgentDeviceView.device_binding_or_unbinding:errLine:{}, errMsg:{}'
  340. .format(e.__traceback__.tb_lineno, repr(e)))
  341. @classmethod
  342. def custom_uid_bindings(cls, request_dict, response):
  343. try:
  344. uid = request_dict.get('uid', None)
  345. customer_name = request_dict.get('customer_name', None)
  346. status = request_dict.get('status', None)
  347. device_mac = request_dict.get('device_mac', None)
  348. page = int(request_dict.get('page', 1))
  349. page_size = int(request_dict.get('pageSize', 20))
  350. filters = Q()
  351. if uid:
  352. filters &= Q(uid__icontains=uid)
  353. if customer_name:
  354. filters &= Q(customer_name__icontains=customer_name)
  355. if status is not None:
  356. filters &= Q(status=status)
  357. if device_mac:
  358. bound_uids = DeviceCustomUID.objects.filter(
  359. device_mac__icontains=device_mac
  360. ).values_list('uid', flat=True)
  361. filters &= Q(uid__in=list(bound_uids))
  362. custom_uid_pool_qs = CustomUIDPool.objects.filter(filters).order_by('-updated_time')
  363. paginator = Paginator(custom_uid_pool_qs, page_size)
  364. page_obj = paginator.page(page)
  365. uid_list = [obj.uid for obj in page_obj]
  366. bindings = DeviceCustomUID.objects.filter(uid__in=uid_list)
  367. # 构建 uid -> 多条绑定记录 map
  368. binding_map = {}
  369. for b in bindings:
  370. binding_map.setdefault(b.uid, []).append(b)
  371. # 构建结果列表
  372. result_list = []
  373. for obj in page_obj:
  374. uid_bindings = binding_map.get(obj.uid, [])
  375. if uid_bindings:
  376. for bind in uid_bindings:
  377. result_list.append({
  378. 'id': obj.id,
  379. 'uid': obj.uid,
  380. 'type': obj.type,
  381. 'customer_name': obj.customer_name,
  382. 'uid_status': obj.status,
  383. 'created_time': obj.created_time,
  384. 'updated_time': obj.updated_time,
  385. 'device_mac': bind.device_mac,
  386. 'device_status': bind.status,
  387. 'bind_time': bind.created_time,
  388. })
  389. else:
  390. result_list.append({
  391. 'id': obj.id,
  392. 'uid': obj.uid,
  393. 'type': obj.type,
  394. 'customer_name': obj.customer_name,
  395. 'uid_status': obj.status,
  396. 'created_time': obj.created_time,
  397. 'updated_time': obj.updated_time,
  398. 'device_mac': '',
  399. 'device_status': None,
  400. 'bind_time': None,
  401. })
  402. return response.json(0, {
  403. 'list': result_list,
  404. 'total': paginator.count
  405. })
  406. except Exception as e:
  407. print(e)
  408. return response.json(500, f'error_line:{e.__traceback__.tb_lineno}, error_msg:{repr(e)}')