فهرست منبع

产品方案API

zhangdongming 3 ماه پیش
والد
کامیت
aeec676fd4
3فایلهای تغییر یافته به همراه427 افزوده شده و 6 حذف شده
  1. 418 0
      AdminController/ProductsSchemeManageController.py
  2. 7 5
      Ansjer/urls.py
  3. 2 1
      requirements.txt

+ 418 - 0
AdminController/ProductsSchemeManageController.py

@@ -0,0 +1,418 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : ProductsSchemeManageController.py
+@Time    : 2025/5/13 11:52
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import datetime
+import json
+import logging
+import time
+from io import BytesIO
+
+import qrcode
+from django.core.paginator import Paginator, EmptyPage
+from django.db import transaction, IntegrityError
+from django.db.models import Q
+from django.http import QueryDict, HttpResponse
+from django.views import View
+
+from Model.models import ProductsScheme
+from Object.RedisObject import RedisObject
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from Ansjer.config import LOGGER
+
+
+class ProductsSchemeManageView(View):
+    def get(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.GET, request, operation)
+
+    def post(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.POST, request, operation)
+
+    def delete(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        delete = QueryDict(request.body)
+        if not delete:
+            delete = request.GET
+        return self.validation(delete, request, operation)
+
+    def put(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        put = QueryDict(request.body)
+        return self.validation(put, request, operation)
+
+    def validation(self, request_dict, request, operation):
+        """请求验证路由"""
+        # 初始化响应对象
+        language = request_dict.get('language', 'cn')
+        response = ResponseObject(language, 'pc')
+
+        # Token验证
+        try:
+            # tko = TokenObject(
+            #     request.META.get('HTTP_AUTHORIZATION'),
+            #     returntpye='pc')
+            # if tko.code != 0:
+            #     return response.json(tko.code)
+            # response.lang = tko.lang
+            # user_id = tko.userID
+            user_id = ''
+        except Exception as e:
+            LOGGER.error(f"Token验证失败: {str(e)}")
+            return response.json(444)
+
+        # 操作路由映射
+        operation_handlers = {
+            'queryList': self.query_list,# 查询列表
+            'add': self.add_scheme,# 添加方案
+            'edit': self.edit_scheme,# 编辑方案
+            'delete': self.delete_scheme,# 删除方案
+            'generateQR': self.generate_qr_code  # 生成二维码
+        }
+
+        handler = operation_handlers.get(operation)
+        if not handler:
+            return response.json(444, 'operation')
+
+        try:
+            return handler(user_id, request_dict, response)
+        except Exception as e:
+            LOGGER.error(f"操作{operation}执行异常:{repr(e)}")
+            return response.json(500, "服务器内部错误")
+    def query_list(self, user_id, request_dict, response):
+        """查询方案列表(优化点:分页封装/字段映射)"""
+        # 参数处理与验证
+        try:
+            page = int(request_dict.get('page', 1))
+            page_size = min(int(request_dict.get('pageSize', 10)), 100)  # 限制最大分页大小
+        except ValueError:
+            return response.json(444, "分页参数错误")
+
+        # 构建查询条件
+        query = Q(deleted=False)
+
+        # 特殊字段处理示例(如果需要)
+        if order_number := request_dict.get('orderNumber'):
+            query &= Q(order_number__icontains=order_number)
+        if storage_code := request_dict.get('storageCode'):
+            query &= Q(storage_code__icontains=storage_code)
+        if device_type := request_dict.get('deviceType'):
+            query &= Q(device_type=int(device_type))
+        if flash := request_dict.get('flash'):
+            query &= Q(flash__icontains=flash)
+        if ddr := request_dict.get('ddr'):
+            query &= Q(ddr__icontains=ddr)
+        if main_controller := request_dict.get('mainController'):
+            query &= Q(main_controller__icontains=main_controller)
+        if wifi := request_dict.get('wifi'):
+            query &= Q(wifi__icontains=wifi)
+        if four_g := request_dict.get('fourG'):
+            query &= Q(four_g__icontains=four_g)
+        if ad := request_dict.get('ad'):
+            query &= Q(ad__icontains=ad)
+        if sensor := request_dict.get('sensor'):
+            query &= Q(sensor__icontains=sensor)
+
+        # 使用分页器
+        queryset = ProductsScheme.objects.filter(query).order_by('-created_time')
+        paginator = Paginator(queryset, page_size)
+
+        try:
+            page_obj = paginator.page(page)
+        except EmptyPage:
+            return response.json(444, "页码超出范围")
+
+        # 序列化数据
+        data = [self._scheme_to_dict(scheme) for scheme in page_obj]
+
+        return response.json(0, {
+            'list': data,
+            'total': paginator.count,
+            'currentPage': page_obj.number,
+            'totalPages': paginator.num_pages
+        })
+
+    @staticmethod
+    def _generate_storage_code(device_type=0):
+        """
+        生成入库编码规则:SC+年月日+3位序列号
+        示例:SC20231125-001
+        特性:
+        1. 每日按设备类型独立计数(Redis原子计数器)
+        2. 自动处理分布式并发
+        3. 序列号每日自动重置
+        """
+        try:
+            redis_conn = RedisObject(2)
+            # 生成 Redis 键名(格式:SC日期:设备类型)
+            date_prefix = datetime.datetime.now().strftime("SC%Y%m%d")
+            redis_key = f"{date_prefix}:{device_type}"
+
+            # 原子操作:递增并获取序列号(自动创建并设置过期时间)
+            sequence = redis_conn.incr(redis_key)
+
+            # 首次创建时设置过期时间(保留两天防止跨天边界问题)
+            if sequence == 1:
+                redis_conn.set_expire(redis_key, 172800)  # 60*60*24*2 = 2天
+
+            # 检查序列号溢出
+            if sequence > 999:
+                raise ValueError(f"当日设备类型 {device_type} 的序列号已超过最大值999")
+
+            return f"{date_prefix}-{sequence:03d}"
+
+        except Exception as e:
+            LOGGER.error(f"生成storage_code失败: {str(e)}")
+            raise
+
+    def add_scheme(self, user_id, request_dict, response):
+        """新增方案(集成自动编码生成)"""
+        try:
+            device_type = int(request_dict.get('deviceType', 0))
+
+            # 生成唯一storage_code
+            storage_code = self._generate_storage_code(device_type)
+
+            # 构造方案数据
+            scheme_data = {
+                'storage_code': storage_code,  # 使用生成的编码
+                'order_number': request_dict.get('orderNumber', ''),
+                'device_type': device_type,
+                'flash': request_dict.get('flash', ''),
+                'ddr': request_dict.get('ddr', ''),
+                'main_controller': request_dict.get('mainController', ''),
+                'wifi': request_dict.get('wifi', ''),
+                'four_g': request_dict.get('fourG', ''),
+                'ad': request_dict.get('ad', ''),
+                'sensor': request_dict.get('sensor', ''),
+                'order_quantity': int(request_dict.get('orderQuantity', 0)),
+                'remark': request_dict.get('remark', ''),
+                'created_time': int(time.time()),
+                'updated_time': int(time.time()),
+                'created_by': user_id,
+                'updated_by': user_id
+            }
+
+            scheme = ProductsScheme.objects.create(**scheme_data)
+            return response.json(0, {
+                'id': scheme.id,
+                'storageCode': storage_code  # 返回生成的编码
+            })
+
+        except IntegrityError as e:
+            LOGGER.error(f"唯一性冲突: {str(e)}")
+            return response.json(173, "数据已存在")
+        except ValueError as e:
+            return response.json(444, "参数类型错误")
+        except Exception as e:
+            LOGGER.exception(f"添加方案异常:{repr(e)}")
+            return response.json(500, "生成编码失败")
+
+        # 可编辑字段白名单(前端字段名: 模型字段名)
+
+    EDITABLE_FIELDS = {
+        'orderNumber': 'order_number',
+        'deviceType': 'device_type',
+        'flash': 'flash',
+        'ddr': 'ddr',
+        'mainController': 'main_controller',
+        'wifi': 'wifi',
+        'fourG': 'four_g',
+        'ad': 'ad',
+        'sensor': 'sensor',
+        'orderQuantity': 'order_quantity',
+        'remark': 'remark'
+    }
+
+    # 需要类型转换的字段配置(字段名: 转换函数)
+    FIELD_CONVERTERS = {
+        'deviceType': int,
+        'orderQuantity': int
+    }
+
+    @transaction.atomic
+    def edit_scheme(self, user_id, request_dict, response):
+        """
+        方案编辑接口(事务原子性保证)
+        优化特性:
+        1. 前端字段到模型字段的自动映射
+        2. 字段级白名单过滤
+        3. 智能类型转换
+        4. 最小化更新字段
+        5. 并发安全控制
+        """
+        # ================= 参数校验阶段 =================
+        if 'id' not in request_dict:
+            return response.json(444, "缺少方案ID")
+
+        try:
+            scheme_id = int(request_dict['id'])
+        except (ValueError, TypeError):
+            LOGGER.warning(f"非法方案ID: {request_dict.get('id')}")
+            return response.json(444, "方案ID格式错误")
+
+        # ================= 数据获取阶段 =================
+        try:
+            # 使用select_for_update锁定记录,防止并发修改
+            scheme = ProductsScheme.objects.select_for_update().get(
+                id=scheme_id,
+                deleted=False
+            )
+        except ProductsScheme.DoesNotExist:
+            LOGGER.info(f"方案不存在: {scheme_id}")
+            return response.json(173, "方案不存在")
+        except Exception as e:
+            LOGGER.error(f"数据库查询异常: {str(e)}")
+            return response.json(500, "系统错误")
+
+        # ================= 数据处理阶段 =================
+        update_fields = []
+        update_data = {'updated_time': int(time.time()), 'updated_by': user_id}
+
+        # 遍历所有请求参数
+        for frontend_field, value in request_dict.items():
+            # 1. 白名单校验
+            if frontend_field not in self.EDITABLE_FIELDS:
+                continue
+
+            # 2. 获取对应的模型字段名
+            model_field = self.EDITABLE_FIELDS[frontend_field]
+
+            # 3. 类型转换处理
+            if frontend_field in self.FIELD_CONVERTERS:
+                try:
+                    value = self.FIELD_CONVERTERS[frontend_field](value)
+                except (ValueError, TypeError):
+                    LOGGER.warning(f"字段类型错误 {frontend_field}={value}")
+                    return response.json(444, f"{frontend_field}类型不合法")
+
+            # 4. 值变更检查(避免无意义更新)
+            if getattr(scheme, model_field) != value:
+                update_data[model_field] = value
+                update_fields.append(model_field)
+
+        # 无实际修改时快速返回
+        if not update_fields:
+            LOGGER.debug("无字段变更")
+            return response.json(0)
+
+        # ================= 数据持久化阶段 =================
+        try:
+            # 动态生成更新SQL:UPDATE ... SET field1=%s, field2=%s
+            ProductsScheme.objects.filter(id=scheme_id).update(**update_data)
+
+            LOGGER.info(f"方案更新成功: {scheme_id} 修改字段: {update_fields}")
+            return response.json(0)
+        except IntegrityError as e:
+            LOGGER.error(f"数据唯一性冲突: {str(e)}")
+            return response.json(177)
+        except Exception as e:
+            LOGGER.exception("方案更新异常")
+            return response.json(500, "系统错误")
+
+    def delete_scheme(self, user_id, request_dict, response):
+        """删除方案(优化点:逻辑删除优化)"""
+        # 参数校验
+        if 'id' not in request_dict:
+            return response.json(444, "缺少方案ID")
+
+        try:
+            # 使用update直接进行逻辑删除,提高效率
+            rows = ProductsScheme.objects.filter(
+                id=request_dict['id'],
+                deleted=False
+            ).update(
+                deleted=True,
+                updated_by=user_id,
+                updated_time=int(time.time())
+            )
+
+            if rows == 0:
+                return response.json(173, "方案不存在")
+
+            return response.json(0)
+        except ValueError:
+            return response.json(500, "方案ID格式错误")
+
+    def _scheme_to_dict(self, scheme):
+        """方案对象序列化(优化点:集中管理序列化逻辑)"""
+        return {
+            'id': scheme.id,
+            'orderNumber': scheme.order_number,
+            'storageCode': scheme.storage_code,
+            'deviceType': scheme.device_type,
+            'flash': scheme.flash,
+            'ddr': scheme.ddr,
+            'mainController': scheme.main_controller,
+            'wifi': scheme.wifi,
+            'fourG': scheme.four_g,
+            'ad': scheme.ad,
+            'sensor': scheme.sensor,
+            'orderQuantity': scheme.order_quantity,
+            'remark': scheme.remark,
+            'createdTime': scheme.created_time,
+            'createdBy': scheme.created_by
+        }
+
+    def generate_qr_code(self, user_id, request_dict, response):
+        """生成方案二维码"""
+        try:
+            scheme_id = int(request_dict.get('scheme_id'))
+            scheme = ProductsScheme.objects.get(id=scheme_id, deleted=False)
+
+            # 创建二维码(示例生成包含方案ID+名称)
+            qr = qrcode.QRCode(
+                version=1,
+                error_correction=qrcode.constants.ERROR_CORRECT_L,
+                box_size=10,
+                border=4,
+            )
+
+            # 组织二维码内容(根据业务需求自定义)
+            qr_data = json.dumps({
+                "storageCode": scheme.storage_code,
+                "type": 'NVR' if scheme.device_type == 1 else 'IPC',
+                "flash": scheme.flash,
+                "DDR": scheme.ddr,
+                "mainController": scheme.main_controller,
+                "wifi": scheme.wifi,
+                "4G": scheme.four_g,
+                "AD": scheme.ad,
+                "sensor": scheme.sensor,
+                "orderQuantity": scheme.order_quantity,
+                "created_time": datetime.datetime.fromtimestamp(scheme.created_time).strftime("%Y-%m-%d %H:%M:%S")
+            })
+            qr.add_data(qr_data)
+            qr.make(fit=True)
+
+            # 生成图片
+            img = qr.make_image(fill_color="black", back_color="white")
+
+            # 将图片转为字节流
+            buffer = BytesIO()
+            img.save(buffer, format="PNG")
+
+            # 返回文件响应
+            return HttpResponse(
+                buffer.getvalue(),
+                content_type="image/png",
+                headers={
+                    'Content-Disposition': f'attachment; filename="scheme_{scheme.id}_qr.png"'
+                }
+            )
+
+        except ProductsScheme.DoesNotExist:
+            return response.json(173, "方案不存在")
+        except Exception as e:
+            LOGGER.error(f"生成二维码失败: {str(e)}")
+            return response.json(500, "生成失败")

+ 7 - 5
Ansjer/urls.py

@@ -5,10 +5,10 @@ from django.urls import re_path, path
 from AdminController import UserManageController, RoleController, MenuController, TestServeController, \
     ServeManagementController, LogManagementController, DeviceManagementController, VersionManagementController, \
     AiServeController, SurveysManageController, SerialManageController, IcloudManagementController, CampaignController, \
-    MessageMangementController, EvaluationActivityController, DataDictManageController
+    MessageMangementController, EvaluationActivityController, DataDictManageController, ProductsSchemeManageController
 from AdminController.CloudServiceManage import AgentDeviceController, AgentCustomerController, AgentOrderController
 from Controller import FeedBack, EquipmentOTA, EquipmentInfo, AdminManage, AppInfo, \
-    Test, MealManage, DeviceManage,  EquipmentStatus, SysManage, DeviceLog, LogAccess, \
+    Test, MealManage, DeviceManage, EquipmentStatus, SysManage, DeviceLog, LogAccess, \
     AppColophon, DateController, \
     EquipmentManager, LogManager, PermissionManager, OTAEquipment, shareUserPermission, UidSetController, \
     UserManger, CheckUserData, \
@@ -279,7 +279,8 @@ urlpatterns = [
     re_path(r'^alexaApi/', include("Ansjer.server_urls.alexa_url")),
     re_path('appCampaign/(?P<operation>.*)', AppCampaignController.AppCampaignView.as_view()),
     re_path('wsParam/(?P<operation>.*)', SmartReplyController.WsParamService.as_view()),
-    re_path('customSubscription/(?P<operation>.*)', UserSubscriptionController.UserSubscriptionControllerView.as_view()),
+    re_path('customSubscription/(?P<operation>.*)',
+            UserSubscriptionController.UserSubscriptionControllerView.as_view()),
     re_path(r'^basic/serialNo/(?P<operation>.*)', SerialNumberCheckController.SerialNumberView.as_view()),
     re_path('inAppPurchase/(?P<operation>.*)', InAppPurchaseController.InAppPurchaseView.as_view()),
     re_path('account/changeAccountInfo/(?P<operation>.*)$', UserController.ChangeAccountInfoView.as_view()),
@@ -296,8 +297,6 @@ urlpatterns = [
     re_path('APNConfig/(?P<operation>.*)', APNConfigController.APNConfigView.as_view()),
     re_path(r'^api/device/custom/(?P<operation>.*)$', DeviceCustomUIDController.DeviceCustomUIDView.as_view()),
 
-
-
     # 后台界面接口 -------------------------------------------------------------------------------------------------------
     # 登录,用户信息,权限
     re_path(r'^login$', UserManageController.LoginView.as_view()),
@@ -344,6 +343,9 @@ urlpatterns = [
     re_path('activityCenter/(?P<operation>.*)', ActivityCenterController.ActivityCenterView.as_view()),
     # 数据字典
     re_path('dataDictView/(?P<operation>.*)', DataDictManageController.DataDictView.as_view()),
+    # 产品方案管理
+    re_path('productsSchemeManage/(?P<operation>.*)',
+            ProductsSchemeManageController.ProductsSchemeManageView.as_view()),
     # 后台界面接口 -------------------------------------------------------------------------------------------------------
 
     # 定时任务接口

+ 2 - 1
requirements.txt

@@ -87,4 +87,5 @@ celery==5.3.6
 django-celery-beat==2.6.0
 oci~=2.125.2
 app-store-server-library==1.3.0
-esdk-obs-python==3.24.6
+esdk-obs-python==3.24.6
+qrcode~=8.2