ProductsSchemeManageController.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @File : ProductsSchemeManageController.py
  4. @Time : 2025/5/13 11:52
  5. @Author : stephen
  6. @Email : zhangdongming@asj6.wecom.work
  7. @Software: PyCharm
  8. """
  9. import datetime
  10. import json
  11. import logging
  12. import time
  13. from io import BytesIO
  14. import qrcode
  15. from django.core.paginator import Paginator, EmptyPage
  16. from django.db import transaction, IntegrityError
  17. from django.db.models import Q
  18. from django.http import QueryDict, HttpResponse
  19. from django.views import View
  20. from Model.models import ProductsScheme, DeviceScheme
  21. from Object.RedisObject import RedisObject
  22. from Object.ResponseObject import ResponseObject
  23. from Object.TokenObject import TokenObject
  24. from Ansjer.config import LOGGER
  25. class ProductsSchemeManageView(View):
  26. def get(self, request, *args, **kwargs):
  27. request.encoding = 'utf-8'
  28. operation = kwargs.get('operation')
  29. return self.validation(request.GET, request, operation)
  30. def post(self, request, *args, **kwargs):
  31. request.encoding = 'utf-8'
  32. operation = kwargs.get('operation')
  33. return self.validation(request.POST, request, operation)
  34. def delete(self, request, *args, **kwargs):
  35. request.encoding = 'utf-8'
  36. operation = kwargs.get('operation')
  37. delete = QueryDict(request.body)
  38. if not delete:
  39. delete = request.GET
  40. return self.validation(delete, request, operation)
  41. def put(self, request, *args, **kwargs):
  42. request.encoding = 'utf-8'
  43. operation = kwargs.get('operation')
  44. put = QueryDict(request.body)
  45. return self.validation(put, request, operation)
  46. def validation(self, request_dict, request, operation):
  47. """请求验证路由"""
  48. # 初始化响应对象
  49. language = request_dict.get('language', 'cn')
  50. response = ResponseObject(language, 'pc')
  51. # Token验证
  52. try:
  53. tko = TokenObject(
  54. request.META.get('HTTP_AUTHORIZATION'),
  55. returntpye='pc')
  56. if tko.code != 0:
  57. return response.json(tko.code)
  58. response.lang = tko.lang
  59. user_id = tko.userID
  60. except Exception as e:
  61. LOGGER.error(f"Token验证失败: {str(e)}")
  62. return response.json(444)
  63. # 操作路由映射
  64. operation_handlers = {
  65. 'queryList': self.query_list,# 查询列表
  66. 'add': self.add_scheme,# 添加方案
  67. 'edit': self.edit_scheme,# 编辑方案
  68. 'delete': self.delete_scheme,# 删除方案
  69. 'generateQR': self.generate_qr_code, # 生成二维码
  70. 'deviceSchemeList': self.device_scheme_list
  71. }
  72. handler = operation_handlers.get(operation)
  73. if not handler:
  74. return response.json(444, 'operation')
  75. try:
  76. return handler(user_id, request_dict, response)
  77. except Exception as e:
  78. LOGGER.error(f"操作{operation}执行异常:{repr(e)}")
  79. return response.json(500, "服务器内部错误")
  80. def query_list(self, user_id, request_dict, response):
  81. """查询方案列表(优化点:分页封装/字段映射)"""
  82. # 参数处理与验证
  83. try:
  84. page = int(request_dict.get('page', 1))
  85. page_size = min(int(request_dict.get('pageSize', 10)), 100) # 限制最大分页大小
  86. except ValueError:
  87. return response.json(444, "分页参数错误")
  88. # 构建查询条件
  89. query = Q(deleted=False)
  90. # 特殊字段处理示例(如果需要)
  91. if order_number := request_dict.get('orderNumber'):
  92. query &= Q(order_number__icontains=order_number)
  93. if storage_code := request_dict.get('storageCode'):
  94. query &= Q(storage_code__icontains=storage_code)
  95. if device_type := request_dict.get('deviceType'):
  96. query &= Q(device_type=int(device_type))
  97. if flash := request_dict.get('flash'):
  98. query &= Q(flash__icontains=flash)
  99. if ddr := request_dict.get('ddr'):
  100. query &= Q(ddr__icontains=ddr)
  101. if main_controller := request_dict.get('mainController'):
  102. query &= Q(main_controller__icontains=main_controller)
  103. if wifi := request_dict.get('wifi'):
  104. query &= Q(wifi__icontains=wifi)
  105. if four_g := request_dict.get('fourG'):
  106. query &= Q(four_g__icontains=four_g)
  107. if ad := request_dict.get('ad'):
  108. query &= Q(ad__icontains=ad)
  109. if sensor := request_dict.get('sensor'):
  110. query &= Q(sensor__icontains=sensor)
  111. # 使用分页器
  112. queryset = ProductsScheme.objects.filter(query).order_by('-created_time')
  113. paginator = Paginator(queryset, page_size)
  114. try:
  115. page_obj = paginator.page(page)
  116. except EmptyPage:
  117. return response.json(444, "页码超出范围")
  118. # 序列化数据
  119. data = [self._scheme_to_dict(scheme) for scheme in page_obj]
  120. return response.json(0, {
  121. 'list': data,
  122. 'total': paginator.count,
  123. 'currentPage': page_obj.number,
  124. 'totalPages': paginator.num_pages
  125. })
  126. @staticmethod
  127. def _generate_storage_code(device_type=0):
  128. """
  129. 生成入库编码规则:SC+年月日+3位序列号
  130. 示例:SC20231125-001
  131. 特性:
  132. 1. 每日按设备类型独立计数(Redis原子计数器)
  133. 2. 自动处理分布式并发
  134. 3. 序列号每日自动重置
  135. """
  136. try:
  137. redis_conn = RedisObject(2)
  138. # 生成 Redis 键名(格式:SC日期:设备类型)
  139. date_prefix = datetime.datetime.now().strftime("SC%Y%m%d")
  140. redis_key = f"{date_prefix}:{device_type}"
  141. # 原子操作:递增并获取序列号(自动创建并设置过期时间)
  142. sequence = redis_conn.incr(redis_key)
  143. # 首次创建时设置过期时间(保留两天防止跨天边界问题)
  144. if sequence == 1:
  145. redis_conn.set_expire(redis_key, 172800) # 60*60*24*2 = 2天
  146. # 检查序列号溢出
  147. if sequence > 999:
  148. raise ValueError(f"当日设备类型 {device_type} 的序列号已超过最大值999")
  149. return f"{date_prefix}-{sequence:03d}"
  150. except Exception as e:
  151. LOGGER.error(f"生成storage_code失败: {str(e)}")
  152. raise
  153. def add_scheme(self, user_id, request_dict, response):
  154. """新增方案(集成自动编码生成)"""
  155. try:
  156. device_type = int(request_dict.get('deviceType', 0))
  157. # 生成唯一storage_code
  158. storage_code = self._generate_storage_code(device_type)
  159. # 构造方案数据
  160. scheme_data = {
  161. 'storage_code': storage_code, # 使用生成的编码
  162. 'order_number': request_dict.get('orderNumber', ''),
  163. 'device_type': device_type,
  164. 'flash': request_dict.get('flash', ''),
  165. 'ddr': request_dict.get('ddr', ''),
  166. 'main_controller': request_dict.get('mainController', ''),
  167. 'wifi': request_dict.get('wifi', ''),
  168. 'four_g': request_dict.get('fourG', ''),
  169. 'ad': request_dict.get('ad', ''),
  170. 'sensor': request_dict.get('sensor', ''),
  171. 'order_quantity': int(request_dict.get('orderQuantity', 0)),
  172. 'customer_code': request_dict.get('customerCode', ''),
  173. 'phy': request_dict.get('phy', ''),
  174. 'remark': request_dict.get('remark', ''),
  175. 'created_time': int(time.time()),
  176. 'updated_time': int(time.time()),
  177. 'created_by': user_id,
  178. 'updated_by': user_id
  179. }
  180. scheme = ProductsScheme.objects.create(**scheme_data)
  181. return response.json(0, {
  182. 'id': scheme.id,
  183. 'storageCode': storage_code # 返回生成的编码
  184. })
  185. except IntegrityError as e:
  186. LOGGER.error(f"唯一性冲突: {str(e)}")
  187. return response.json(173, "数据已存在")
  188. except ValueError as e:
  189. return response.json(444, "参数类型错误")
  190. except Exception as e:
  191. LOGGER.exception(f"添加方案异常:{repr(e)}")
  192. return response.json(500, "生成编码失败")
  193. # 可编辑字段白名单(前端字段名: 模型字段名)
  194. EDITABLE_FIELDS = {
  195. 'orderNumber': 'order_number',
  196. 'deviceType': 'device_type',
  197. 'flash': 'flash',
  198. 'ddr': 'ddr',
  199. 'mainController': 'main_controller',
  200. 'wifi': 'wifi',
  201. 'fourG': 'four_g',
  202. 'ad': 'ad',
  203. 'sensor': 'sensor',
  204. 'orderQuantity': 'order_quantity',
  205. 'customerCode': 'customer_code',
  206. 'phy': 'phy',
  207. 'remark': 'remark'
  208. }
  209. # 需要类型转换的字段配置(字段名: 转换函数)
  210. FIELD_CONVERTERS = {
  211. 'deviceType': int,
  212. 'orderQuantity': int
  213. }
  214. @transaction.atomic
  215. def edit_scheme(self, user_id, request_dict, response):
  216. """
  217. 方案编辑接口(事务原子性保证)
  218. 优化特性:
  219. 1. 前端字段到模型字段的自动映射
  220. 2. 字段级白名单过滤
  221. 3. 智能类型转换
  222. 4. 最小化更新字段
  223. 5. 并发安全控制
  224. """
  225. # ================= 参数校验阶段 =================
  226. if 'id' not in request_dict:
  227. return response.json(444, "缺少方案ID")
  228. try:
  229. scheme_id = int(request_dict['id'])
  230. except (ValueError, TypeError):
  231. LOGGER.warning(f"非法方案ID: {request_dict.get('id')}")
  232. return response.json(444, "方案ID格式错误")
  233. # ================= 数据获取阶段 =================
  234. try:
  235. # 使用select_for_update锁定记录,防止并发修改
  236. scheme = ProductsScheme.objects.select_for_update().get(
  237. id=scheme_id,
  238. deleted=False
  239. )
  240. except ProductsScheme.DoesNotExist:
  241. LOGGER.info(f"方案不存在: {scheme_id}")
  242. return response.json(173, "方案不存在")
  243. except Exception as e:
  244. LOGGER.error(f"数据库查询异常: {str(e)}")
  245. return response.json(500, "系统错误")
  246. # ================= 数据处理阶段 =================
  247. update_fields = []
  248. update_data = {'updated_time': int(time.time()), 'updated_by': user_id}
  249. # 遍历所有请求参数
  250. for frontend_field, value in request_dict.items():
  251. # 1. 白名单校验
  252. if frontend_field not in self.EDITABLE_FIELDS:
  253. continue
  254. # 2. 获取对应的模型字段名
  255. model_field = self.EDITABLE_FIELDS[frontend_field]
  256. # 3. 类型转换处理
  257. if frontend_field in self.FIELD_CONVERTERS:
  258. try:
  259. value = self.FIELD_CONVERTERS[frontend_field](value)
  260. except (ValueError, TypeError):
  261. LOGGER.warning(f"字段类型错误 {frontend_field}={value}")
  262. return response.json(444, f"{frontend_field}类型不合法")
  263. # 4. 值变更检查(避免无意义更新)
  264. if getattr(scheme, model_field) != value:
  265. update_data[model_field] = value
  266. update_fields.append(model_field)
  267. # 无实际修改时快速返回
  268. if not update_fields:
  269. LOGGER.debug("无字段变更")
  270. return response.json(0)
  271. # ================= 数据持久化阶段 =================
  272. try:
  273. # 动态生成更新SQL:UPDATE ... SET field1=%s, field2=%s
  274. ProductsScheme.objects.filter(id=scheme_id).update(**update_data)
  275. LOGGER.info(f"方案更新成功: {scheme_id} 修改字段: {update_fields}")
  276. return response.json(0)
  277. except IntegrityError as e:
  278. LOGGER.error(f"数据唯一性冲突: {str(e)}")
  279. return response.json(177)
  280. except Exception as e:
  281. LOGGER.exception("方案更新异常")
  282. return response.json(500, "系统错误")
  283. def delete_scheme(self, user_id, request_dict, response):
  284. """删除方案(优化点:逻辑删除优化)"""
  285. # 参数校验
  286. if 'id' not in request_dict:
  287. return response.json(444, "缺少方案ID")
  288. try:
  289. # 使用update直接进行逻辑删除,提高效率
  290. rows = ProductsScheme.objects.filter(
  291. id=request_dict['id'],
  292. deleted=False
  293. ).update(
  294. deleted=True,
  295. updated_by=user_id,
  296. updated_time=int(time.time())
  297. )
  298. if rows == 0:
  299. return response.json(173, "方案不存在")
  300. return response.json(0)
  301. except ValueError:
  302. return response.json(500, "方案ID格式错误")
  303. def _scheme_to_dict(self, scheme):
  304. """方案对象序列化(优化点:集中管理序列化逻辑)"""
  305. return {
  306. 'id': scheme.id,
  307. 'orderNumber': scheme.order_number,
  308. 'storageCode': scheme.storage_code,
  309. 'deviceType': scheme.device_type,
  310. 'flash': scheme.flash,
  311. 'ddr': scheme.ddr,
  312. 'mainController': scheme.main_controller,
  313. 'wifi': scheme.wifi,
  314. 'fourG': scheme.four_g,
  315. 'ad': scheme.ad,
  316. 'sensor': scheme.sensor,
  317. 'orderQuantity': scheme.order_quantity,
  318. 'customerCode': scheme.customer_code,
  319. 'phy':scheme.phy,
  320. 'remark': scheme.remark,
  321. 'createdTime': scheme.created_time,
  322. 'createdBy': scheme.created_by
  323. }
  324. def generate_qr_code(self, user_id, request_dict, response):
  325. """生成方案二维码"""
  326. try:
  327. scheme_id = int(request_dict.get('scheme_id'))
  328. scheme = ProductsScheme.objects.get(id=scheme_id, deleted=False)
  329. # 创建二维码(示例生成包含方案ID+名称)
  330. qr = qrcode.QRCode(
  331. version=1,
  332. error_correction=qrcode.constants.ERROR_CORRECT_L,
  333. box_size=10,
  334. border=4,
  335. )
  336. # 组织二维码内容(根据业务需求自定义)
  337. qr_data = json.dumps({
  338. "storageCode": scheme.storage_code,
  339. "type": 'NVR' if scheme.device_type == 1 else 'IPC',
  340. "flash": scheme.flash,
  341. "DDR": scheme.ddr,
  342. "mainController": scheme.main_controller,
  343. "wifi": scheme.wifi,
  344. "4G": scheme.four_g,
  345. "AD": scheme.ad,
  346. "sensor": scheme.sensor,
  347. 'customerCode': scheme.customer_code,
  348. 'phy': scheme.phy,
  349. "orderQuantity": scheme.order_quantity,
  350. "created_time": datetime.datetime.fromtimestamp(scheme.created_time).strftime("%Y-%m-%d %H:%M:%S")
  351. })
  352. qr.add_data(qr_data)
  353. qr.make(fit=True)
  354. # 生成图片
  355. img = qr.make_image(fill_color="black", back_color="white")
  356. # 将图片转为字节流
  357. buffer = BytesIO()
  358. img.save(buffer, format="PNG")
  359. # 返回文件响应
  360. return HttpResponse(
  361. buffer.getvalue(),
  362. content_type="image/png",
  363. headers={
  364. 'Content-Disposition': f'attachment; filename="scheme_{scheme.id}_qr.png"'
  365. }
  366. )
  367. except ProductsScheme.DoesNotExist:
  368. return response.json(173, "方案不存在")
  369. except Exception as e:
  370. LOGGER.error(f"生成二维码失败: {str(e)}")
  371. return response.json(500, "生成失败")
  372. @staticmethod
  373. def device_scheme_list(user_id, request_dict, response):
  374. """
  375. 查询设备方案列表
  376. @param request_dict: 请求参数
  377. @param response: 响应对象
  378. @return: 响应对象包含设备方案列表
  379. """
  380. serial_number = request_dict.get("serialNumber", None)
  381. storage_code = request_dict.get("storageCode", None)
  382. device_type = request_dict.get("deviceType", None)
  383. phone_model = request_dict.get("phoneModel", None)
  384. page = request_dict.get("page", 1)
  385. page_size = request_dict.get("pageSize", 20)
  386. try:
  387. # 获取设备方案查询集
  388. device_scheme_qs = DeviceScheme.objects.all()
  389. # 过滤条件
  390. if serial_number:
  391. device_scheme_qs = device_scheme_qs.filter(serial_number=serial_number)
  392. if storage_code:
  393. device_scheme_qs = device_scheme_qs.filter(storage_code__icontains=storage_code)
  394. if device_type:
  395. device_scheme_qs = device_scheme_qs.filter(device_type=device_type)
  396. if phone_model:
  397. device_scheme_qs = device_scheme_qs.filter(phone_model__icontains=phone_model)
  398. # 分页
  399. paginator = Paginator(device_scheme_qs.order_by('-created_time'), page_size)
  400. device_schemes = paginator.page(page)
  401. # 构建返回数据
  402. device_list = []
  403. for device in device_schemes.object_list:
  404. # 获取关联的产品方案信息
  405. product_scheme = ProductsScheme.objects.filter(storage_code=device.storage_code).first()
  406. device_data = {
  407. 'id': device.id,
  408. 'serialNumber': device.serial_number,
  409. 'deviceType': device.device_type,
  410. 'phoneModel': device.phone_model,
  411. 'storageCode': device.storage_code,
  412. 'createdTime': device.created_time,
  413. 'updatedTime': device.updated_time,
  414. }
  415. if product_scheme:
  416. device_data.update({
  417. 'orderNumber': product_scheme.order_number if product_scheme.order_number else '',
  418. 'type': 'NVR' if product_scheme.device_type == 1 else 'IPC',
  419. 'flash': product_scheme.flash if product_scheme.flash else '',
  420. 'ddr': product_scheme.ddr if product_scheme.ddr else '',
  421. 'mainController': product_scheme.main_controller if product_scheme.main_controller else '',
  422. 'wifi': product_scheme.wifi if product_scheme.wifi else '',
  423. 'fourG': product_scheme.four_g if product_scheme.four_g else '',
  424. 'ad': product_scheme.ad if product_scheme.ad else '',
  425. 'phy': product_scheme.phy if product_scheme.phy else '',
  426. 'sensor': product_scheme.sensor if product_scheme.sensor else '',
  427. 'orderQuantity': product_scheme.order_quantity if product_scheme.order_quantity else '',
  428. 'customerCode': product_scheme.customer_code if product_scheme.customer_code else '',
  429. 'remark': product_scheme.remark if product_scheme.remark else ''
  430. })
  431. device_list.append(device_data)
  432. data = {
  433. 'list': device_list,
  434. 'total': paginator.count,
  435. }
  436. return response.json(0, data)
  437. except Exception as e:
  438. print(e)
  439. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))