Sfoglia il codice sorgente

Merge branch 'test' of http://192.168.136.99:3000/servers/ASJServer into test

lang 3 anni fa
parent
commit
be78412b30
47 ha cambiato i file con 5191 aggiunte e 718 eliminazioni
  1. 189 27
      AdminController/DeviceManagementController.py
  2. 5 28
      AdminController/LogManagementController.py
  3. 5 3
      AdminController/SerialManageController.py
  4. 13 5
      AdminController/ServeManagementController.py
  5. 3 0
      Ansjer/cn_config/config_formal.py
  6. 3 0
      Ansjer/cn_config/config_test.py
  7. 36 2
      Ansjer/config.py
  8. 3 0
      Ansjer/eur_config/config_formal.py
  9. 3 0
      Ansjer/local_config/config_local.py
  10. 4 4
      Ansjer/local_config/local_settings.py
  11. 21 0
      Ansjer/server_urls/loocam_url.py
  12. 15 0
      Ansjer/server_urls/unicom_url.py
  13. 16 6
      Ansjer/urls.py
  14. 3 0
      Ansjer/us_config/config_formal.py
  15. 204 116
      Controller/AiController.py
  16. 62 10
      Controller/AppSetController.py
  17. 31 190
      Controller/CloudStorage.py
  18. 59 38
      Controller/CouponController.py
  19. 21 6
      Controller/DetectControllerV2.py
  20. 31 31
      Controller/DeviceConfirmRegion.py
  21. 23 1
      Controller/EquipmentInfo.py
  22. 4 1
      Controller/EquipmentManagerV2.py
  23. 24 54
      Controller/IotCoreController.py
  24. 119 0
      Controller/MessagePush/EquipmentMessagePush.py
  25. 3 1
      Controller/OrderContrller.py
  26. 5 1
      Controller/PaymentCycle.py
  27. 948 0
      Controller/SensorGateway/EquipmentFamilyController.py
  28. 263 0
      Controller/SensorGateway/GatewayDeviceController.py
  29. 551 0
      Controller/SensorGateway/GatewayFamilyMemberController.py
  30. 253 0
      Controller/SensorGateway/GatewayFamilyRoomController.py
  31. 0 0
      Controller/SensorGateway/SensorGatewayController.py
  32. 362 0
      Controller/SensorGateway/SubDeviceController.py
  33. 41 43
      Controller/SerialNumberController.py
  34. 74 35
      Controller/TestApi.py
  35. 264 0
      Controller/UnicomCombo/UnicomComboController.py
  36. 1 1
      Controller/UserBrandController.py
  37. 471 71
      Model/models.py
  38. 8 16
      Object/IOTCore/IotObject.py
  39. 3 1
      Object/ResponseObject.py
  40. 256 0
      Object/UnicomObject.py
  41. 237 0
      Object/utils/SM3Util.py
  42. 216 0
      Object/utils/SymmetricCryptoUtil.py
  43. 66 22
      Service/CommonService.py
  44. 90 4
      Service/EquipmentInfoService.py
  45. 1 1
      Service/MiscellService.py
  46. 181 0
      Service/PayService.py
  47. BIN
      requirements.txt

+ 189 - 27
AdminController/DeviceManagementController.py

@@ -5,18 +5,18 @@ import time
 
 import oss2
 from django.db import transaction
-from django.db.models import Q
+from django.db.models import Q, F
+from django.views.generic.base import View
 
 from Ansjer.config import OSS_STS_ACCESS_KEY, OSS_STS_ACCESS_SECRET
+from Model.models import Device_Info, UidSetModel, LogModel, UID_Bucket, Unused_Uid_Meal, Order_Model, StsCrdModel, \
+    VodHlsModel, ExperienceContextModel, DeviceTypeModel, Equipment_Info, UidUserModel, ExperienceAiModel, AiService, \
+    AppBundle, App_Info, AppDeviceType, DeviceNameLanguage
 from Object.ResponseObject import ResponseObject
 from Object.TokenObject import TokenObject
-from django.views.generic.base import View
-
+from Service.CommonService import CommonService
 from Service.EquipmentInfoService import EquipmentInfoService
 from Service.ModelService import ModelService
-from Service.CommonService import CommonService
-from Model.models import Device_Info, UidSetModel, LogModel, UID_Bucket, Unused_Uid_Meal, Order_Model, StsCrdModel, \
-    VodHlsModel, ExperienceContextModel, DeviceTypeModel, Equipment_Info, UidUserModel
 
 
 class DeviceManagement(View):
@@ -35,8 +35,11 @@ class DeviceManagement(View):
         response = ResponseObject(language, 'pc')
         if operation == 'addDeviceType':
             return self.addDeviceType(request, request_dict, response)
-        elif operation == 'delDeviceData':
-            return self.delDeviceData(request_dict, response)
+        elif operation == 'delDeviceData':  # 删除设备数据
+            return self.del_device_data(request_dict, response)
+        elif operation == 'getDeviceIcon':  # app获取设备图标
+            response = ResponseObject(language)
+            return self.get_device_icon(request_dict, response)
         else:
             tko = TokenObject(
                 request.META.get('HTTP_AUTHORIZATION'),
@@ -47,23 +50,33 @@ class DeviceManagement(View):
             userID = tko.userID
             if not userID:
                 return response.json(444, 'userID')
-            if operation == 'getDeviceInfoList':
-                return self.getDeviceInfoList(request_dict, response)
+            if operation == 'getDeviceInfoList':  # 获取设备信息数据
+                return self.get_device_info_list(request_dict, response)
             elif operation == 'deleteDevice':
                 return self.deleteDevice(request_dict, response)
-            elif operation == 'resetVod':
+            elif operation == 'resetVod':  # 重置云存
                 return self.resetVod(request, request_dict, response)
+            elif operation == 'resetAi':  # 重置AI
+                return self.reset_ai(request, request_dict, response)
             elif operation == 'resetPrimaryUser':
                 return self.resetPrimaryUser(request, request_dict, response)
             elif operation == 'getDeviceTypeList':
                 return self.getDeviceTypeList(request_dict, response)
             elif operation == 'deleteDeviceType':
                 return self.deleteDeviceType(request_dict, response)
+            elif operation == 'getAppDeviceTypeList':  # 获取app设备类型数据
+                return self.get_app_device_type_list(request_dict, response)
+            elif operation == 'getAppBundleIdList':  # 获取app包id数据
+                return self.get_app_bundle_id_list(response)
+            elif operation == 'editAppDeviceType':  # 编辑app设备类型数据
+                return self.edit_app_device_type(request_dict, response)
+            elif operation == 'deleteAppDeviceType':  # 删除app设备类型数据
+                return self.delete_app_device_type(request_dict, response)
             else:
                 return response.json(444, 'operation')
 
-    # 获取设备信息数据
-    def getDeviceInfoList(self, request_dict, response):
+    @staticmethod
+    def get_device_info_list(request_dict, response):
         pageNo = request_dict.get('pageNo', None)
         pageSize = request_dict.get('pageSize', None)
         UID = request_dict.get('UID', None)
@@ -78,7 +91,7 @@ class DeviceManagement(View):
         line = int(pageSize)
 
         try:
-            if UID or serialNumber or NickName or username:    # 条件查询
+            if UID or serialNumber or NickName or username:  # 条件查询
                 if UID:
                     device_info_qs = Device_Info.objects.filter(UID__contains=UID)
                 if serialNumber:
@@ -89,11 +102,11 @@ class DeviceManagement(View):
                     device_info_qs = Device_Info.objects.filter(Q(userID__username__contains=username) |
                                                                 Q(userID__userEmail__contains=username) |
                                                                 Q(userID__phone__contains=username))
-                total = len(device_info_qs)
+                total = device_info_qs.count()
                 if not total:
                     return response.json(0, {'list': {}, 'total': 0})
                 device_info_qs = device_info_qs[(page - 1) * line:page * line]
-            else:   # 查询全部
+            else:  # 查询全部
                 total = Device_Info.objects.filter().count()
                 device_info_qs = Device_Info.objects.filter()[(page - 1) * line:page * line]
                 if not device_info_qs.exists():
@@ -106,17 +119,20 @@ class DeviceManagement(View):
                         # 设备的用户名和主用户
                         username = ModelService.get_user_name(device_info_list["datas"][k]['fields']['userID'])
                         device_info_list["datas"][k]['fields']['username'] = username
-                        device_info_list["datas"][k]['fields']['vodPrimaryMaster'] = device_info_list["datas"][k]['fields']['vodPrimaryMaster']
+                        device_info_list["datas"][k]['fields']['vodPrimaryMaster'] = \
+                            device_info_list["datas"][k]['fields']['vodPrimaryMaster']
                         # 设备类型,是否支持Alexa和ip
                         type = device_info_list["datas"][k]['fields']['Type']
                         device_type_qs = DeviceTypeModel.objects.filter(type=type).values('name')
                         if device_type_qs.exists():
                             device_info_list["datas"][k]['fields']['Type'] = device_type_qs[0]['name']
-                        uid_set_qs = UidSetModel.objects.filter(uid=device_info_list["datas"][k]['fields']['UID']).values('is_alexa', 'ip')
+                        uid_set_qs = UidSetModel.objects.filter(
+                            uid=device_info_list["datas"][k]['fields']['UID']).values('is_alexa', 'ip', 'version')
                         if uid_set_qs.exists():
                             isAlexa = '是' if uid_set_qs[0]['is_alexa'] else '否'
                             device_info_list["datas"][k]['fields']['isAlexa'] = isAlexa
                             device_info_list["datas"][k]['fields']['ip'] = uid_set_qs[0]['ip']
+                            device_info_list["datas"][k]['fields']['version'] = uid_set_qs[0]['version']
             return response.json(0, {'list': device_info_list, 'total': total})
         except Exception as e:
             print(e)
@@ -135,7 +151,7 @@ class DeviceManagement(View):
                 Device_Info.objects.filter(id=deviceID).delete()
                 # 删除推送消息
                 for val in range(1, 8):
-                    EquipmentInfoService.get_equipment_info_model('', val).\
+                    EquipmentInfoService.get_equipment_info_model('', val). \
                         filter(device_user_id=userID, device_uid=uid).delete()
             return response.json(0)
         except Exception as e:
@@ -182,14 +198,14 @@ class DeviceManagement(View):
                 'time': int(time.time()),
                 'url': 'deviceManagement/resetVod',
                 'content': json.dumps(content),
-                'operation': '{}重置设备云存'.format(uid),
+                'operation': '{}重置云存'.format(uid),
             }
             with transaction.atomic():
                 LogModel.objects.create(**log)
                 # 删除和更新设备云存相关数据
                 UID_Bucket.objects.filter(uid=uid).delete()
                 Unused_Uid_Meal.objects.filter(uid=uid).delete()
-                Order_Model.objects.filter(UID=uid).delete()
+                Order_Model.objects.filter(UID=uid, order_type=0).delete()
                 StsCrdModel.objects.filter(uid=uid).delete()
                 VodHlsModel.objects.filter(uid=uid).delete()
                 ExperienceContextModel.objects.filter(uid=uid).delete()
@@ -199,6 +215,34 @@ class DeviceManagement(View):
             print(e)
             return response.json(500, repr(e))
 
+    @staticmethod
+    def reset_ai(request, request_dict, response):
+        uid = request_dict.get('uid', None)
+        if not uid:
+            return response.json(444)
+        try:
+            # 记录操作日志
+            ip = CommonService.get_ip_address(request)
+            content = json.loads(json.dumps(request_dict))
+            log = {
+                'ip': ip,
+                'user_id': 2,
+                'status': 200,
+                'time': int(time.time()),
+                'url': 'deviceManagement/resetAi',
+                'content': json.dumps(content),
+                'operation': '{}重置AI'.format(uid),
+            }
+            with transaction.atomic():
+                LogModel.objects.create(**log)
+                # 删除和更新设备AI相关数据
+                ExperienceAiModel.objects.filter(uid=uid).delete()
+                AiService.objects.filter(uid=uid).delete()
+            return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(500, repr(e))
+
     # 获取设备类型数据
     def getDeviceTypeList(self, request_dict, response):
         name = request_dict.get('name', None)
@@ -263,7 +307,7 @@ class DeviceManagement(View):
             # 上传文件到阿里云OSS
             auth = oss2.Auth(OSS_STS_ACCESS_KEY, OSS_STS_ACCESS_SECRET)
             bucket = oss2.Bucket(auth, 'oss-cn-shenzhen.aliyuncs.com', 'ansjer-static-resources')
-            key = 'device_type/' + icon     # 图片文件存放于 device_type 目录下
+            key = 'device_type/' + icon  # 图片文件存放于 device_type 目录下
             # https://oss.console.aliyun.com/bucket/oss-cn-shenzhen/ansjer-static-resources/object?path=device_type%2F
             bucket.put_object(key=key, data=iconFile)
             DeviceTypeModel.objects.create(name=name, model=model, type=type, ptz_type=ptz_type, icon=icon,
@@ -273,8 +317,8 @@ class DeviceManagement(View):
             print(e)
             return response.json(500, repr(e))
 
-    # 删除设备数据
-    def delDeviceData(self, request_dict, response):
+    @staticmethod
+    def del_device_data(request_dict, response):
         uidList = request_dict.get('uidList', None)
         delDataOptions = request_dict.get('delDataOptions', None)
 
@@ -282,7 +326,7 @@ class DeviceManagement(View):
             return response.json(444)
         try:
             with transaction.atomic():
-                uidList = uidList.splitlines()      # 按行('\r', '\r\n', \n')切割字符串返回列表
+                uidList = uidList.splitlines()  # 按行('\r', '\r\n', \n')切割字符串返回列表
                 # 根据删除项删除相关数据
                 if '设备信息数据' in delDataOptions:
                     Device_Info.objects.filter(UID__in=uidList).delete()
@@ -292,15 +336,133 @@ class DeviceManagement(View):
                     Equipment_Info.objects.filter(devUid__in=uidList).delete()
                 if '设备AP信息数据' in delDataOptions:
                     UidUserModel.objects.filter(UID__in=uidList).delete()
+                if '设备AI数据' in delDataOptions:
+                    ExperienceAiModel.objects.filter(uid__in=uidList).delete()
+                    Order_Model.objects.filter(UID__in=uidList, order_type=1).delete()
                 if '设备云存数据' in delDataOptions:
                     UID_Bucket.objects.filter(uid__in=uidList).delete()
-                    Unused_Uid_Meal.objects.filter(uid__in=uidList).delete()
-                    Order_Model.objects.filter(UID__in=uidList).delete()
                     StsCrdModel.objects.filter(uid__in=uidList).delete()
                     VodHlsModel.objects.filter(uid__in=uidList).delete()
+                    Unused_Uid_Meal.objects.filter(uid__in=uidList).delete()
                     ExperienceContextModel.objects.filter(uid__in=uidList).delete()
+                    Order_Model.objects.filter(UID__in=uidList, order_type=0).delete()
                     Device_Info.objects.filter(UID__in=uidList).update(vodPrimaryUserID='', vodPrimaryMaster='')
             return response.json(0)
         except Exception as e:
             print(e)
             return response.json(500, repr(e))
+
+    @staticmethod
+    def get_device_icon(request_dict, response):
+        lang = request_dict.get('lang', None)
+        app_bundle_id = request_dict.get('appBundleId', None)
+
+        if not all([lang, app_bundle_id]):
+            return response.json(444)
+        try:
+            app_bundle_qs = AppBundle.objects.filter(app_bundle_id=app_bundle_id,
+                                                     app_device_type__devicenamelanguage__lang=lang).annotate(
+                model=F('app_device_type__model'), type=F('app_device_type__type'), icon=F('app_device_type__icon'),
+                name=F('app_device_type__devicenamelanguage__name'),
+                sort=F('app_device_type__devicenamelanguage__sort')).order_by(
+                'app_device_type__devicenamelanguage__sort').values('model', 'type', 'icon', 'name', 'sort')
+            dvr_list = [app_bundle for app_bundle in app_bundle_qs if app_bundle['model'] == 1]
+            ipc_list = [app_bundle for app_bundle in app_bundle_qs if app_bundle['model'] == 2]
+            print(dvr_list, ipc_list)
+            res = {
+                'deviceDvr': dvr_list,
+                'deviceIpc': ipc_list,
+            }
+            return response.json(0, res)
+        except Exception as e:
+            print(e)
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def get_app_device_type_list(request_dict, response):
+        app_bundle_id = request_dict.get('appBundleId', None)
+        lang = request_dict.get('lang', 'cn')
+
+        pageNo = request_dict.get('pageNo', None)
+        pageSize = request_dict.get('pageSize', None)
+
+        if not all([pageNo, pageSize]):
+            return response.json(444)
+
+        page = int(pageNo)
+        line = int(pageSize)
+        try:
+            if not app_bundle_id:
+                app_bundle_id = 'com.ansjer.zccloud'
+            app_bundle_qs = AppBundle.objects.filter(app_bundle_id=app_bundle_id)
+            if lang:
+                app_bundle_qs = app_bundle_qs.filter(app_device_type__devicenamelanguage__lang=lang)
+
+            app_bundle_qs = app_bundle_qs.annotate(
+                model=F('app_device_type__model'), type=F('app_device_type__type'), icon=F('app_device_type__icon'),
+                lang=F('app_device_type__devicenamelanguage__lang'),
+                name=F('app_device_type__devicenamelanguage__name'),
+                sort=F('app_device_type__devicenamelanguage__sort')).values('id', 'app_device_type__id', 'model',
+                                                                            'type', 'icon',
+                                                                            'app_device_type__devicenamelanguage__id',
+                                                                            'lang', 'name', 'sort').order_by(
+                'app_device_type__devicenamelanguage__sort')
+            if not app_bundle_qs.exists():
+                return response.json(0)
+            total = app_bundle_qs.count()
+            app_bundle_qs = app_bundle_qs[(page - 1) * line:page * line]
+            app_device_type_list = [app_bundle for app_bundle in app_bundle_qs]
+            return response.json(0, {'list': app_device_type_list, 'total': total})
+        except Exception as e:
+            print(e)
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def get_app_bundle_id_list(response):
+        try:
+            app_info_qs = App_Info.objects.filter().values('id', 'appBundleId', 'appName')
+            appBundleIdList = list(app_info_qs)
+            return response.json(0, {'list': appBundleIdList})
+        except Exception as e:
+            print(e)
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def edit_app_device_type(request_dict, response):
+        # app_device_type表数据
+        app_device_type_id = request_dict.get('app_device_type__id', None)
+        model = request_dict.get('model', None)
+        type = request_dict.get('type', None)
+        icon = request_dict.get('icon', None)
+        # device_name_language表数据
+        device_name_language_id = request_dict.get('app_device_type__devicenamelanguage__id', None)
+        lang = request_dict.get('lang', None)
+        name = request_dict.get('name', None)
+        sort = request_dict.get('sort', None)
+
+        if not all([app_device_type_id, model, type, icon, device_name_language_id, lang, name, sort]):
+            return response.json(444)
+
+        try:
+            with transaction.atomic():
+                AppDeviceType.objects.filter(id=app_device_type_id).update(model=model, type=type, icon=icon)
+                DeviceNameLanguage.objects.filter(id=device_name_language_id).update(lang=lang, name=name, sort=sort)
+            return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def delete_app_device_type(request_dict, response):
+        app_bundle_id = request_dict.get('appBundleId', None)
+        app_device_type_id = request_dict.get('appDeviceTypeId', None)
+        if not all([app_bundle_id, app_device_type_id]):
+            return response.json(444)
+        try:
+            app_bundle_qs = AppBundle.objects.get(id=app_bundle_id)
+            app_device_type_qs = AppDeviceType.objects.filter(id=app_device_type_id)
+            app_bundle_qs.app_device_type.remove(*app_device_type_qs)
+            return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(500, repr(e))

+ 5 - 28
AdminController/LogManagementController.py

@@ -154,8 +154,9 @@ class LogManagementView(View):
             print(e)
             return response.json(500, repr(e))
 
-    def requestPublishMqtt(self, request_dict, response):
-        # 通用发布MQTT主题通知
+    # 通用发布MQTT通知
+    @staticmethod
+    def requestPublishMqtt(request_dict, response):
         msg = request_dict.get('msg', None)
         thing_name = request_dict.get('thing_name', None)
         topic_name = request_dict.get('topic_name', None)
@@ -163,34 +164,10 @@ class LogManagementView(View):
             return response.json(444)
 
         try:
-            # 获取数据组织将要请求的url
-            iot = iotdeviceInfoModel.objects.filter(
-                thing_name=thing_name).values(
-                'endpoint', 'token_iot_number')
-            if not iot.exists():
-                return response.json(10043)
-            endpoint = iot[0]['endpoint']
-            Token = iot[0]['token_iot_number']
-
-            # api doc: https://docs.aws.amazon.com/zh_cn/iot/latest/developerguide/http.html
-            # url: https://IoT_data_endpoint/topics/url_encoded_topic_name?qos=1
-            # post请求url发布MQTT消息
-            url = 'https://{}/topics/{}'.format(endpoint, topic_name)
-            authorizer_name = 'Ansjer_Iot_Auth'
-            signature = CommonService.rsa_sign(Token)  # Token签名
-            headers = {
-                'x-amz-customauthorizer-name': authorizer_name,
-                'Token': Token,
-                'x-amz-customauthorizer-signature': signature}
             msg = eval(msg)
-            r = requests.post(url=url, headers=headers, json=msg, timeout=2)
-            if r.status_code == 200:
-                res = r.json()
-                if res['message'] == 'OK':
-                    return response.json(0)
-                return response.json(10044)
-            else:
+            if not CommonService.req_publish_mqtt_msg(thing_name, topic_name, msg):
                 return response.json(10044)
+            return response.json(0)
         except Exception as e:
             return response.json(500, repr(e))
 

+ 5 - 3
AdminController/SerialManageController.py

@@ -15,7 +15,7 @@ from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic.base import View
 
-from Model.models import CompanyModel, SerialNumberModel, VPGModel, UIDModel, UIDCompanySerialModel, CompanySerialModel, \
+from Model.models import CompanyModel, VPGModel, UIDModel, UIDCompanySerialModel, CompanySerialModel, \
     LogModel
 from Object.ResponseObject import ResponseObject
 from Object.TokenObject import TokenObject
@@ -182,7 +182,7 @@ class SerialView(View):
             if not company_serial_qs.exists():
                 return response.json(0, {'list': '', 'total': 0})
             total = company_serial_qs.count()
-            serial_number_page = company_serial_qs.order_by('-serial_number')[(page - 1) * line:page * line].\
+            serial_number_page = company_serial_qs.order_by('-serial_number')[(page - 1) * line:page * line]. \
                 values('serial_number', 'company__name', 'status', 'add_time', 'update_time')
             return response.json(0, {'list': list(serial_number_page), 'total': total})
         except Exception as e:
@@ -241,7 +241,7 @@ class SerialView(View):
         try:
             query = UIDModel.objects.filter()
             if serial_number:
-                company_serial_qs = CompanySerialModel.objects.filter(serial_number=serial_number).values()
+                company_serial_qs = CompanySerialModel.objects.filter(serial_number__contains=serial_number).values()
                 if company_serial_qs.exists():
                     cs_id = str(company_serial_qs[0]['id'])
                     uid_company_serial_qs = UIDCompanySerialModel.objects.filter(company_serial_id=cs_id).values(
@@ -249,6 +249,8 @@ class SerialView(View):
                     if uid_company_serial_qs.exists():
                         uid_id = uid_company_serial_qs[0]['uid_id']
                         query = query.filter(id=uid_id)
+                else:
+                    return response.json(0, {'list': '', 'total': 0})
             if vpg_id:
                 query = query.filter(vpg_id=int(vpg_id))
             if status:

+ 13 - 5
AdminController/ServeManagementController.py

@@ -14,7 +14,7 @@ from boto3.session import Session
 from django.http import JsonResponse, HttpResponseRedirect, HttpResponse, StreamingHttpResponse
 from django.views.generic.base import View
 from Model.models import Device_Info, Role, MenuModel, VodBucketModel, CDKcontextModel, Store_Meal, Order_Model, \
-    UID_Bucket, ExperienceContextModel, Lang, Device_User, CloudLogModel, UidSetModel
+    UID_Bucket, ExperienceContextModel, Lang, Device_User, CloudLogModel, UidSetModel, Unused_Uid_Meal
 from Object.ResponseObject import ResponseObject
 from Object.TokenObject import TokenObject
 from Object.UidTokenObject import UidTokenObject
@@ -845,16 +845,24 @@ class serveManagement(View):
             uid_bucket_qs = uid_bucket_qs.order_by('-addTime')[(page - 1) * line:page * line]
 
             for uid_bucket in uid_bucket_qs:
+                # 套餐到期时间累加未使用套餐
+                unused_qs = Unused_Uid_Meal.objects.filter(uid=uid_bucket.uid).values('num', 'expire')
+                if unused_qs.exists():
+                    addMonth = 0
+                    for unused in unused_qs:
+                        addMonth += unused['num'] * unused['expire']
+                    endTime = CommonService.calcMonthLater(addMonth, uid_bucket.endTime)
+                    endTime = time.strftime("%Y--%m--%d %H:%M:%S", time.localtime(endTime))
+                else:
+                    endTime = time.strftime("%Y--%m--%d %H:%M:%S", time.localtime(uid_bucket.endTime))
+
                 uid = uid_bucket.uid.upper()
                 data = {
                     'id': uid_bucket.id,
                     'uid': uid,
                     'channel': uid_bucket.channel,
                     'status': uid_bucket.status,
-                    'endTime': time.strftime(
-                        "%Y--%m--%d %H:%M:%S",
-                        time.localtime(
-                            uid_bucket.endTime)),
+                    'endTime': endTime,
                     'addTime': time.strftime(
                         "%Y--%m--%d %H:%M:%S",
                         time.localtime(

+ 3 - 0
Ansjer/cn_config/config_formal.py

@@ -13,6 +13,9 @@
 """
 import os
 
+# 配置信息
+CONFIG_INFO = 'cn'
+
 NGINX_RTMP_STAT = 'http://www.zositechc.cn/stat'
 SERVER_DOMAIN_SSL = 'https://www.zositechc.cn/'
 SERVER_DOMAIN = 'http://www.zositechc.cn/'

+ 3 - 0
Ansjer/cn_config/config_test.py

@@ -13,6 +13,9 @@
 """
 import os
 
+# 配置信息
+CONFIG_INFO = 'test'
+
 NGINX_RTMP_STAT = 'http://test.zositechc.cn/stat/'
 SERVER_DOMAIN = 'http://test.zositechc.cn/'
 SERVER_DOMAIN_SSL = 'https://test.zositechc.cn/'

+ 36 - 2
Ansjer/config.py

@@ -12,7 +12,14 @@
 @Contact: chanjunkai@163.com
 """
 # 主要静态变量配置文件
-import datetime, os
+import os
+import datetime
+
+# 配置信息
+CONFIG_TEST = 'test'
+CONFIG_CN = 'cn'
+CONFIG_US = 'us'
+CONFIG_EUR = 'eur'
 
 SERVER_TYPE = os.environ.get('DJANGO_SETTINGS_MODULE')
 print(SERVER_TYPE)
@@ -364,6 +371,9 @@ JPUSH_CONFIG = {
     'com.ansjer.customizedc_a': {
         'Key': 'ecdde95cd272f410ee029139',
         'Secret': '9ddca1e92bfa331126fd8826'},
+    'com.ansjer.customizedf_a': {
+        'Key': '6ddbfb8fd7a0984dfba74a00',
+        'Secret': '971ee5c5facfbe76cb31a686'},
 }
 # type =1
 FCM_CONFIG = {
@@ -377,6 +387,7 @@ FCM_CONFIG = {
     'com.ansjer.accloud_a': 'AAAAb9YP3rk:APA91bFm06w8b9OKQ0gz0iaWFuRqRIkvgAz6z7Gp3dBU_X-LNGJQd1hc1QR2W7QzBglF8SHtERA45a2lbdLRa5qv7hxfd6W_sJLBK7dA8jklsOQBvy505oUzTwMKWy4TwH-exps9KrhO',
     'com.ansjer.zccloud_ab': 'AAAAb9YP3rk:APA91bHu8u-CTpcd0g6lKPo0WNVqCi8jZub1cPPbSAY9AucT1HxlF65ZDUko9iG8q2ch17bwu9YWHpK1xI1gHSRXCslLvZlXEmHZC0AG3JKg15XuUvlFKACIajUFV-pOeGRT8tM6-31I',
     'com.ansjer.customizedc_a': 'AAAAb9YP3rk:APA91bHu8u-CTpcd0g6lKPo0WNVqCi8jZub1cPPbSAY9AucT1HxlF65ZDUko9iG8q2ch17bwu9YWHpK1xI1gHSRXCslLvZlXEmHZC0AG3JKg15XuUvlFKACIajUFV-pOeGRT8tM6-31I',
+    'com.ansjer.customizedf_a': 'AAAAb9YP3rk:APA91bFm06w8b9OKQ0gz0iaWFuRqRIkvgAz6z7Gp3dBU_X-LNGJQd1hc1QR2W7QzBglF8SHtERA45a2lbdLRa5qv7hxfd6W_sJLBK7dA8jklsOQBvy505oUzTwMKWy4TwH-exps9KrhO',
 }
 APNS_CONFIG = {
     'com.ansjer.loocamccloud': {
@@ -403,6 +414,9 @@ APNS_CONFIG = {
     'com.ansjer.customizede': {
         'pem_path': 'AnsjerPush/file/apns_pem/customizede.pem',
     },
+    'com.ansjer.customizedh': {
+        'pem_path': 'AnsjerPush/file/apns_pem/customizedh.pem',
+    },
 }
 APNS_MODE = 'dev'
 SERVER_TYPE = os.environ.get('DJANGO_SETTINGS_MODULE')
@@ -433,4 +447,24 @@ APNS_CODE = {
 APP_TYPE = {
     1: 'ios',
     2: 'android'
-}
+}
+
+# 联通Unicom config
+# 应用地址,请向开发人员索取
+unicomAppUrl = 'https://chinaunicom-iot.unimip.cn'
+# 应用id,请向开发人员创建应用,分配appId
+unicomAppId = 'LNO7ojB7mZU5ep9IuiHvygVWbwaO9vt0'
+# 应用密匙,请向开发人员创建应用,分配appSecret
+unicomAppSecret = 'p8MT1CS4QAVl0xb4cp7VOCgMvtQp2O43'
+# 租户/产品ID,请向开发人员索取
+unicomTenantId = '1469181295663300608'
+# 密码加密密匙,请向开发人员索取
+unicomEncodeKey = 'uBdUx82vPHkDKb284d7NkjFoNcKWBuka'
+# 密码加密参数,请向开发人员索取
+unicomIvKey = 'oCRU0D1wKfBlNeTz'
+# 用户名,如无请联系客户经理开通
+unicomUserName = 'zh-zhasjdz'
+# 登录密码,如无请联系客户经理
+unicomPassword = 'XoROL1vCl5@_'
+# 回调签名验证加密密匙,请向开发人员索取
+unicomPushKey = 'NVVDQMBSOJTXA'

+ 3 - 0
Ansjer/eur_config/config_formal.py

@@ -13,6 +13,9 @@
 """
 import os
 
+# 配置信息
+CONFIG_INFO = 'eur'
+
 NGINX_RTMP_STAT = 'http://www.dvema.com/stat'
 SERVER_DOMAIN_SSL = 'https://www.dvema.com/'
 SERVER_DOMAIN = 'http://www.dvema.com/'

+ 3 - 0
Ansjer/local_config/config_local.py

@@ -3,6 +3,9 @@
 
 import os
 
+# 配置信息
+CONFIG_INFO = 'test'
+
 OAUTH_ACCESS_TOKEN_SECRET = 'local_a+jbgnw%@1%zy^=@dn62%'
 OAUTH_REFRESH_TOKEN_SECRET = 'local_r+jbgnw%@1%zy^=@dn62%'
 

+ 4 - 4
Ansjer/local_config/local_settings.py

@@ -97,16 +97,16 @@ WSGI_APPLICATION = 'Ansjer.local_config.local_wsgi.application'
 # DATABASES_PASS = 'UKv78ezQhiGMmSef5U5s'
 
 # 本地数据库
-DATABASE_DATA = 'ansjer_test'
+DATABASE_DATA = 'ansjer'
 SERVER_HOST = '127.0.0.1'
 DATABASES_USER = 'root'
-DATABASES_PASS = 'root'
+DATABASES_PASS = '123456'
 
 # 推送数据库
-DATABASE_DATA2 = 'ansjerpush'
+DATABASE_DATA2 = 'push'
 SERVER_HOST2 = '127.0.0.1'
 DATABASES_USER2 = 'root'
-DATABASES_PASS2 = 'root'
+DATABASES_PASS2 = '123456'
 
 # 序列号公共数据库
 # DATABASE_DATA3 = 'ansjer_test'

+ 21 - 0
Ansjer/server_urls/loocam_url.py

@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : loocam_url.py
+@Time    : 2022/5/20 11:44
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+from django.urls import re_path
+
+from Controller.SensorGateway import GatewayFamilyRoomController, SubDeviceController, GatewayFamilyMemberController, \
+    EquipmentFamilyController, GatewayDeviceController
+
+urlpatterns = [
+    re_path(r'^sensor/gateway/(?P<operation>.*)$', EquipmentFamilyController.EquipmentFamilyView.as_view()),
+    re_path(r'^gateway/family/room/(?P<operation>.*)$', GatewayFamilyRoomController.GatewayFamilyRoomView.as_view()),
+    re_path(r'^gateway/family/member/(?P<operation>.*)$',
+            GatewayFamilyMemberController.GatewayFamilyMemberView.as_view()),
+    re_path(r'^gateway/subdevice/(?P<operation>.*)$', SubDeviceController.GatewaySubDeviceView.as_view()),
+    re_path(r'^gateway/device/info/(?P<operation>.*)$', GatewayDeviceController.GatewayDeviceView.as_view()),
+]

+ 15 - 0
Ansjer/server_urls/unicom_url.py

@@ -0,0 +1,15 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : unicom_url.py
+@Time    : 2022/6/23 9:50
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+from django.urls import re_path
+
+from Controller.UnicomCombo import UnicomComboController
+
+urlpatterns = [
+    re_path(r'^api/(?P<operation>.*)$', UnicomComboController.UnicomComboView.as_view()),
+]

+ 16 - 6
Ansjer/urls.py

@@ -1,7 +1,10 @@
-from django.contrib import admin
 from django.conf.urls import url
+from django.contrib import admin
 from django.urls import path, re_path
 
+from AdminController import UserManageController, RoleController, MenuController, TestServeController, \
+    ServeManagementController, LogManagementController, DeviceManagementController, VersionManagementController, \
+    AiServeController, SurveysManageController, SerialManageController
 from Controller import FeedBack, EquipmentOTA, EquipmentInfo, AdminManage, AppInfo, \
     AccessLog, DynamoDBLog, Test, MealManage, DeviceManage, EquipmentStatus, SysManage, DeviceLog, LogAccess, \
     AppColophon, DateController, \
@@ -20,12 +23,11 @@ from Controller import FeedBack, EquipmentOTA, EquipmentInfo, AdminManage, AppIn
     RegionController, VPGController, LanguageController, TestController, DeviceConfirmRegion, S3GetStsController, \
     DetectControllerV2, ShadowController, TestDetectController, PcInfo, PctestController, DeviceDebug, PaymentCycle, \
     DeviceLogController, CouponController, AiController
-from Controller.Surveys import CloudStorageController
 from Controller.Cron import CronTaskController
-from AdminController import UserManageController, RoleController, MenuController, TestServeController, \
-    ServeManagementController, LogManagementController, DeviceManagementController, VersionManagementController, \
-    AiServeController, SurveysManageController, SerialManageController
-from SensorGateway import SensorGatewayController
+from Controller.MessagePush import EquipmentMessagePush
+from Controller.Surveys import CloudStorageController
+from Controller.SensorGateway import SensorGatewayController, EquipmentFamilyController
+from django.urls import include
 
 urlpatterns = [
     url(r'^testApi/(?P<operation>.*)$', TestApi.testView.as_view()),
@@ -242,6 +244,8 @@ urlpatterns = [
 
     # AI服务
     url(r'^AiService/(?P<operation>.*)$', AiController.AiView.as_view()),
+    # 消息提醒
+    url(r'^app/setting/notification/(?P<operation>.*)$', EquipmentMessagePush.EquipmentMessagePushView.as_view()),
 
     # 新增解密的接口
     url(r'^v3/account/changePwd$', UserController.v3ChangePwdView.as_view()),
@@ -359,6 +363,12 @@ urlpatterns = [
     # 问卷调查
     url(r'^api/surveys/(?P<operation>.*)$', CloudStorageController.CloudStorageView.as_view()),
 
+    # 网关家庭模块
+    url(r'^app/sensor/gateway/(?P<operation>.*)$', EquipmentFamilyController.EquipmentFamilyView.as_view()),
+    url(r'^loocam/', include("Ansjer.server_urls.loocam_url")),
+    # 联通4G套餐模块
+    url(r'^unicom/', include("Ansjer.server_urls.unicom_url")),
+
     # 传感器网关
     re_path('sensorGateway/(?P<operation>.*)', SensorGatewayController.SensorGateway.as_view()),
 

+ 3 - 0
Ansjer/us_config/config_formal.py

@@ -13,6 +13,9 @@
 """
 import os
 
+# 配置信息
+CONFIG_INFO = 'us'
+
 NGINX_RTMP_STAT = 'http://www.dvema.com/stat'
 SERVER_DOMAIN_SSL = 'https://www.dvema.com/'
 SERVER_DOMAIN = 'http://www.dvema.com/'

+ 204 - 116
Controller/AiController.py

@@ -11,62 +11,37 @@
 @file: cloudstorage.py
 @Contact: chanjunkai@163.com
 """
-import base64
-import json
+import logging
 import os
 import time
-import glob
-import urllib
 from urllib.parse import quote, parse_qs, unquote
 
 import apns2
 import boto3
+import botocore
 import jpush
-import oss2
 import paypalrestsdk
-import threading
-import calendar
-import datetime
-import logging
-import sys
-import requests
-from aliyunsdkcore import client
-from aliyunsdksts.request.v20150401 import AssumeRoleRequest
 from boto3.session import Session
-from django.http import JsonResponse, HttpResponseRedirect, HttpResponse
+from botocore import client
 from django.db import transaction
+from django.db.models import Q, F, Sum
+from django.http import HttpResponseRedirect, HttpResponse
 from django.views.generic.base import View
-import jwt
-from Object.ETkObject import ETkObject
 from pyfcm import FCMNotification
-from Ansjer.config import OSS_STS_ACCESS_KEY, OSS_STS_ACCESS_SECRET, OSS_ROLE_ARN, SERVER_DOMAIN, PAYPAL_CRD, \
-    SERVER_DOMAIN_SSL, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ARN, APNS_MODE, APNS_CONFIG, BASE_DIR, \
-    JPUSH_CONFIG, FCM_CONFIG, OAUTH_ACCESS_TOKEN_SECRET, DETECT_PUSH_DOMAINS
-from Controller.CheckUserData import DataValid
-from Model.models import Device_Info, Order_Model, Store_Meal, VodHlsModel, OssCrdModel, UID_Bucket, StsCrdModel, \
-    ExperienceContextModel, Pay_Type, CDKcontextModel, Device_User, SysMassModel, SysMsgModel, UidPushModel, \
-    Unused_Uid_Meal, UIDMainUser, UserModel, PromotionRuleModel, VideoPlaybackTimeModel, CloudLogModel, CouponModel, \
-    AiStoreMeal, AiService, UidSetModel, Ai_Push_Info, iotdeviceInfoModel, AiProcessTime, Equipment_Info
-from Object.AWS.S3Email import S3Email
+
+from Ansjer.config import PAYPAL_CRD, \
+    SERVER_DOMAIN_SSL, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, APNS_MODE, APNS_CONFIG, BASE_DIR, \
+    JPUSH_CONFIG, FCM_CONFIG, DETECT_PUSH_DOMAINS
+from Model.models import Device_Info, Order_Model, ExperienceAiModel, Pay_Type, CDKcontextModel, UidPushModel, \
+    AiStoreMeal, AiService, UidSetModel, Ai_Push_Info
 from Object.AliPayObject import AliPayObject
-from Object.AliSmsObject import AliSmsObject
+from Object.ETkObject import ETkObject
 from Object.RedisObject import RedisObject
 from Object.ResponseObject import ResponseObject
 from Object.TokenObject import TokenObject
-from Object.UidTokenObject import UidTokenObject
-from Service.CommonService import CommonService
-from Object.m3u8generate import PlaylistGenerator
 from Object.WechatPayObject import WechatPayObject
-from django.db.models import Q, F, Count, Sum
-from Controller.PaymentCycle import Paypal
-from decimal import Decimal
-from Ansjer.config import SERVER_TYPE
+from Service.CommonService import CommonService
 from Service.ModelService import ModelService
-from Object import MergePic
-import boto3
-import botocore
-from botocore import client
-
 
 
 # AI服务
@@ -106,7 +81,7 @@ class AiView(View):
             elif operation == 'getAiStatus':  # 获取AI开关状态
                 return self.getAiStatus(userID, request_dict, response)
             elif operation == 'commoditylist':  # 获取AI套餐列表
-                return self.do_commodity_list(userID, request_dict, response)
+                return self.do_commodity_list(request_dict, response)
             elif operation == 'queryInfo':  # 查询消息列表
                 return self.queryInfo(userID, request_dict, response)
             elif operation == 'readInfo':  # 消息已读
@@ -117,6 +92,8 @@ class AiView(View):
                 return self.do_querylist(userID, request_dict, response)
             elif operation == 'getUsingPackage':  # 获取当前使用的ai套餐
                 return self.getUsingPackage(request_dict, userID, response)
+            elif operation == 'experienceOrder':  # 体验AI套餐
+                return self.experience_order(request_dict, userID, response)
             else:
                 return response.json(414)
 
@@ -163,21 +140,23 @@ class AiView(View):
             if nowTime > endTime:
                 return response.json(10054)
 
-            dvqs = Device_Info.objects.filter(userID_id=userID, UID=uid)
+            # 查询设备是否属于该用户
+            device_info_qs = Device_Info.objects.filter(userID_id=userID, UID=uid)
+            if not device_info_qs.exists():
+                return response.json(14)
             status = int(status)
             nowTime = int(time.time())
-            if not dvqs.exists():
-                return response.json(14)
+
             uid_set_qs = UidSetModel.objects.filter(uid=uid)
             if uid_set_qs.exists():
                 uid_set_id = uid_set_qs[0].id
+                interval = uid_set_qs[0].new_detect_interval if not interval else interval
                 qs_data = {
                     'updTime': nowTime,
                 }
                 if interval:
                     qs_data['detect_interval'] = int(interval)
-                if detect_group:
-                    qs_data['detect_group'] = detect_group
+                    qs_data['detect_group'] = detect_group if detect_group else ''
                 uid_set_qs.update(**qs_data)
             else:
                 qs_data = {
@@ -187,23 +166,23 @@ class AiView(View):
                 }
                 if interval:
                     qs_data['detect_interval'] = int(interval)
-                if detect_group:
-                    qs_data['detect_group'] = detect_group
+                qs_data['detect_group'] = detect_group if detect_group else ''
                 # 添加设备配置
                 uid_set_qs = UidSetModel.objects.create(**qs_data)
                 uid_set_id = uid_set_qs.id
 
-            qs_data['detect_status'] = status       # ai开关状态
+            qs_data['detect_status'] = status  # ai开关状态
             ai_service_qs.update(**qs_data)
-            topic_name = 'ansjer/generic/{}'.format(uid)
-            if status == 0:     # 关闭
+            thing_name = CommonService.query_serial_with_uid(uid)   # 存在序列号则为使用序列号作为物品名
+            topic_name = 'ansjer/generic/{}'.format(thing_name)
+            if status == 0:  # 关闭
                 # mqtt通知设备关闭AI识别功能
                 msg = {'commandType': 'AIDisable'}
-                req_success = CommonService.req_publish_mqtt_msg(uid, topic_name, msg)
+                req_success = CommonService.req_publish_mqtt_msg(thing_name, topic_name, msg)
                 if not req_success:
                     return response.json(10044)
                 return response.json(0)
-            elif status == 1:       # 开启
+            elif status == 1:  # 开启
                 uid_push_qs = UidPushModel.objects.filter(userID_id=userID, m_code=m_code, uid_set__uid=uid)
 
                 if uid_push_qs.exists():
@@ -237,7 +216,8 @@ class AiView(View):
                 etkObj = ETkObject(etk='')
                 etk = etkObj.encrypt(uid)
 
-                aiIdentificationUrl = "{DETECT_PUSH_DOMAIN}AiService/identification".format(DETECT_PUSH_DOMAIN=DETECT_PUSH_DOMAINS)
+                aiIdentificationUrl = "{DETECT_PUSH_DOMAIN}AiService/identification".format(
+                    DETECT_PUSH_DOMAIN=DETECT_PUSH_DOMAINS)
 
                 # mqtt通知设备开启AI识别功能
                 msg = {
@@ -248,7 +228,7 @@ class AiView(View):
                               'aiIdentificationUrl': aiIdentificationUrl,
                           }
                       },
-                req_success = CommonService.req_publish_mqtt_msg(uid, topic_name, msg)
+                req_success = CommonService.req_publish_mqtt_msg(thing_name, topic_name, msg)
                 if not req_success:
                     return response.json(10044)
                 return response.json(0, {'aiIdentificationUrl': aiIdentificationUrl, 'endTime': endTime, 'etk': etk})
@@ -273,7 +253,9 @@ class AiView(View):
         except Exception as e:
             return response.json(500, repr(e))
 
-    def do_commodity_list(self, userID, request_dict, response):
+    # 获取AI套餐列表
+    @staticmethod
+    def do_commodity_list(request_dict, response):
         uid = request_dict.get('uid', None)
         lang = request_dict.get('lang', 'en')
 
@@ -283,10 +265,18 @@ class AiView(View):
             if device_info_qs.exists():
                 return response.json(0)
 
+            # 没免费体验过的设备只返回体验套餐数据,体验过的不再返回
+            exc_ai_qs = ExperienceAiModel.objects.filter(uid=uid, experience_type=0)
+            if exc_ai_qs.exists():
+                ai_meal_qs = AiStoreMeal.objects.filter(~Q(pay_type=10))
+            else:
+                ai_meal_qs = AiStoreMeal.objects.filter(pay_type=10)
+
             # 查询套餐数据
-            ai_meal_qs = AiStoreMeal.objects.filter(is_show=1, lang__lang=lang).\
-                annotate(ai_meal_id=F('id'), title=F('lang__title'), content=F('lang__content')).\
-                values("ai_meal_id", "title", "content", "price", "effective_day", "currency", "virtual_price", "symbol")
+            ai_meal_qs = ai_meal_qs.filter(is_show=1, lang__lang=lang). \
+                annotate(ai_meal_id=F('id'), title=F('lang__title'), content=F('lang__content')). \
+                values("ai_meal_id", "title", "content", "price", "effective_day", "currency", "virtual_price",
+                       "symbol")
             if not ai_meal_qs.exists():
                 return response.json(0)
 
@@ -323,8 +313,10 @@ class AiView(View):
             count = omqs.count()
             omqs = omqs.annotate(rank__title=F('ai_rank__lang__title'), rank__content=F('ai_rank__lang__content'),
                                  rank__day=F('ai_rank__effective_day'), rank__price=F('ai_rank__price'),
-                                 rank__expire=F('ai_rank__effective_day'), rank__id=F('ai_rank_id'), rank__currency=F('ai_rank__currency'))
-            order_ql = omqs[(page - 1) * line:page * line].values("orderID", "UID", "channel", "desc", "price", "currency",
+                                 rank__expire=F('ai_rank__effective_day'), rank__id=F('ai_rank_id'),
+                                 rank__currency=F('ai_rank__currency'))
+            order_ql = omqs[(page - 1) * line:page * line].values("orderID", "UID", "channel", "desc", "price",
+                                                                  "currency",
                                                                   "addTime",
                                                                   "updTime", "paypal", "rank__day", "payType",
                                                                   "rank__price", "status",
@@ -366,23 +358,119 @@ class AiView(View):
                 return response.json(0, [])
 
             # 计算套餐过期时间
-            sum_end_time = AiService.objects.filter(Q(uid=uid), ~Q(use_status=2)).aggregate(Sum('endTime'))['endTime__sum']
-            ai_service_qs = ai_service_qs.order_by('addTime').annotate(bucket__content=F('orders__ai_rank__lang__title')).\
+            sum_end_time = AiService.objects.filter(Q(uid=uid), ~Q(use_status=2)).aggregate(Sum('endTime'))[
+                'endTime__sum']
+            ai_service_qs = ai_service_qs.order_by('addTime').annotate(
+                bucket__content=F('orders__ai_rank__lang__title')). \
                 values('uid', 'use_status', 'bucket__content')
+
             ai_service_data = ai_service_qs[0]
+            # 如果存在序列号返回完整序列号
+            device_info_qs = Device_Info.objects.filter(UID=uid).values('serial_number', 'Type')
+            serial_number = device_info_qs[0]['serial_number']
+            if serial_number:
+                ai_service_data['uid'] = CommonService.get_full_serial_number(uid, serial_number, device_info_qs[0]['Type'])
+
             ai_service_data['endTime'] = sum_end_time
             return response.json(0, [ai_service_data])
         except Exception as e:
             print(e)
             return response.json(500, repr(e))
 
+    # 体验AI套餐
+    @staticmethod
+    def experience_order(request_dict, userID, response):
+        uid = request_dict.get('uid', None)
+        channel = request_dict.get('channel', None)
+        pay_type = int(request_dict.get('pay_type', None))
+        rank = request_dict.get('rank', None)
+        cdk = request_dict.get('cdk', None)
+        lang = request_dict.get('lang', 'en')
+
+        # 使用redis设置唯一key加锁
+        redisObj = RedisObject()
+        redis_key = uid + 'do_experience_ai_order'
+        isLock = redisObj.CONN.setnx(redis_key, 1)
+        redisObj.CONN.expire(redis_key, 60)
+        if not isLock:
+            return response.json(5)
+        try:
+            if pay_type == 10:  # 判断是否已体验过套餐
+                exc_ai_qs = ExperienceAiModel.objects.filter(uid=uid, experience_type=0)
+                if exc_ai_qs.exists():
+                    return response.json(5)
+
+            if cdk is not None and pay_type == 11:
+                cdk_qs = CDKcontextModel.objects.filter(cdk=cdk).values('is_activate', 'rank__id', 'rank__commodity_code')
+                if not cdk_qs.exists():
+                    return response.json(10040)
+                if cdk_qs[0]['is_activate'] == 1:
+                    return response.json(10039)
+                rank = cdk_qs[0]['rank__id']
+
+            if uid is None or channel is None or pay_type is None or rank is None:
+                redisObj.del_data(key=redis_key)
+                return response.json(444)
+
+            # 判断是否为主用户操作
+            device_info_qs = Device_Info.objects.filter(Q(UID=uid) & ~Q(vodPrimaryUserID='')).values('vodPrimaryUserID')
+            if device_info_qs.exists():
+                if device_info_qs[0]['vodPrimaryUserID'] != userID:
+                    if pay_type == 10:
+                        return response.json(10035)
+                    if pay_type == 11:
+                        return response.json(10036)
+
+            dv_qs = Device_Info.objects.filter(userID_id=userID, UID=uid, isShare=False, isExist=1)
+            if not dv_qs.exists():
+                return response.json(12)
+
+            orderID = CommonService.createOrderID()
+            nowTime = int(time.time())
+            ai_store_meal_qs = AiStoreMeal.objects.filter(id=rank, lang__lang=lang, is_show=1). \
+                values('lang__content', 'price', 'currency', 'effective_day')
+            if not ai_store_meal_qs.exists():
+                return response.json(173)
+
+            effective_day = ai_store_meal_qs[0]['effective_day']
+            endTime = nowTime + effective_day * 24 * 60 * 60  # 套餐结束时间
+
+            with transaction.atomic():
+                # 订单表创建数据
+                Order_Model.objects.create(orderID=orderID, UID=uid, channel=channel, userID_id=userID,
+                                           desc=ai_store_meal_qs[0]['lang__content'], payType=pay_type, payTime=nowTime,
+                                           price=ai_store_meal_qs[0]['price'], currency=ai_store_meal_qs[0]['currency'],
+                                           addTime=nowTime, updTime=nowTime, pay_url='AI体验',
+                                           rank_id=1, ai_rank_id=rank, status=1)
+                # ai服务表创建数据
+                AiService.objects.create(uid=uid, channel=channel, orders_id=orderID, detect_status=1, endTime=endTime,
+                                         addTime=nowTime, updTime=nowTime, use_status=1)
+
+                if pay_type == 10:
+                    ExperienceAiModel.objects.create(
+                        experience_type=0,
+                        uid=uid,
+                        do_time=nowTime
+                    )
+
+                elif pay_type == 11:
+                    CDKcontextModel.objects.filter(cdk=cdk).update(is_activate=1, order=orderID)
+
+                redisObj.del_data(key=redis_key)
+                pay_ok_url = "{}cloudstorage/payOK?paytype={}&lang={}".format(SERVER_DOMAIN_SSL, pay_type, lang)
+                return response.json(0, pay_ok_url)
+        except Exception as e:
+            print(e)
+            redisObj.del_data(key=redis_key)
+            return response.json(474)
+
     def do_create_pay_order(self, request_dict, request, userID, response):
         uid = request_dict.get('uid', None)
         channel = request_dict.get('channel', None)
         pay_type = int(request_dict.get('pay_type', 1))
-        ai_meal_id = request_dict.get('ai_meal_id', None)
+        rank = request_dict.get('rank', None)
         lang = request_dict.get('lang', 'en')
-        if not uid or not channel or not pay_type or not ai_meal_id:
+        if not uid or not channel or not pay_type or not rank:
             return response.json(444)
 
         try:
@@ -400,7 +488,7 @@ class AiView(View):
             #         return response.json(10033)
 
             # 获取ai套餐数据
-            ai_sm_qs = AiStoreMeal.objects.filter(id=ai_meal_id, pay_type=pay_type, is_show=1, lang__lang=lang). \
+            ai_sm_qs = AiStoreMeal.objects.filter(id=rank, pay_type=pay_type, is_show=1, lang__lang=lang). \
                 values('lang__title', 'lang__content', 'currency', 'price')
             if not ai_sm_qs.exists():
                 return response.json(173)
@@ -425,20 +513,22 @@ class AiView(View):
                 'currency': currency,
                 'addTime': nowTime,
                 'updTime': nowTime,
-                'ai_rank_id': ai_meal_id,
+                'ai_rank_id': rank,
                 'rank_id': 1,
                 'order_type': 1,
             }
 
-            if pay_type == 1:       # PayPal支付
-                order_dict['paymentID'], order_dict['pay_url'] = self.create_paypal_payment(lang, orderID, price, currency, content, response)
+            if pay_type == 1:  # PayPal支付
+                order_dict['paymentID'], order_dict['pay_url'] = self.create_paypal_payment(lang, orderID, price,
+                                                                                            currency, content, response)
                 res_data = {'redirectUrl': order_dict['pay_url'], 'orderID': orderID}
-            elif pay_type == 2:     # 支付宝
+            elif pay_type == 2:  # 支付宝
                 order_dict['pay_url'] = self.create_alipay_payment(lang, orderID, price, title, content, response)
                 res_data = {'redirectUrl': order_dict['pay_url'], 'orderID': orderID}
-            elif pay_type == 3:     # 微信支付
+            elif pay_type == 3:  # 微信支付
                 ip = CommonService.get_ip_address(request)
-                order_dict['pay_url'], sign_params = self.create_wechat_payment(lang, orderID, price, ip, content, response)
+                order_dict['pay_url'], sign_params = self.create_wechat_payment(lang, orderID, price, ip, content,
+                                                                                response)
                 res_data = {'redirectUrl': order_dict['pay_url'], 'orderID': orderID, 'result': sign_params}
             else:
                 return response.json(444, {'param': 'pay_type'})
@@ -591,8 +681,8 @@ class AiView(View):
             attach = data["attach"]
             parmap = dict([(k, v[0]) for k, v in parse_qs(unquote(attach)).items()])
             lang = parmap['lang']
-            trade_status = data['result_code']      # 业务结果  SUCCESS/FAIL
-            orderID = data['out_trade_no']          # 商户订单号
+            trade_status = data['result_code']  # 业务结果  SUCCESS/FAIL
+            orderID = data['out_trade_no']  # 商户订单号
             order_qs = Order_Model.objects.filter(orderID=orderID, status=0)
             if not order_qs.exists():
                 return response.json(173)
@@ -634,7 +724,6 @@ class AiView(View):
                            'detect_status': 1,
                            'addTime': nowTime,
                            'updTime': nowTime,
-                           'detect_group': '1'
                            }
         if ai_service_qs.exists():  # 有正在使用的套餐,套餐结束时间保存为套餐有效期
             ai_service_dict['endTime'] = effective_day * 24 * 60 * 60
@@ -672,13 +761,13 @@ class AiView(View):
             '2': ['Dog', 'Pet', 'Canine', 'Animal', 'Puppy'],  # 动物
             '3': ['Car', 'Vehicle', 'Transportation', 'Automobile']  # 车
         }
-        #找出识别的所有标签
+        # 找出识别的所有标签
         for label in labels:
             label_name.append(label['Name'])
             for Parents in label['Parents']:
                 label_name.append(Parents['Name'])
 
-        #删除用户没有选择的ai识别类型, 并且得出最终识别结果
+        # 删除用户没有选择的ai识别类型, 并且得出最终识别结果
         user_detect_list = user_detect_group.split(',')
         user_detect_list = [i.strip() for i in user_detect_list]
         label_result_list = []
@@ -690,56 +779,56 @@ class AiView(View):
                     if label in label_name:
                         label_result_list.append(label)
 
-        #找出标签边框线位置信息
+        # 找出标签边框线位置信息
         boundingBoxList = []
         for label in labels:
             if label['Name'] in label_result_list:
                 for boundingBox in label['Instances']:
                     boundingBoxList.append(boundingBox['BoundingBox'])
 
-        #找出边框位置信息对应的单图位置并重新计算位置比
+        # 找出边框位置信息对应的单图位置并重新计算位置比
         merge_image_height = image_size['height']
         merge_image_width = image_size['width']
-        single_height = merge_image_height//image_size['num']
+        single_height = merge_image_height // image_size['num']
         new_bounding_box_dict = {}
-        new_bounding_box_dict[n_time+'_0'] = []
-        new_bounding_box_dict[n_time+'_1'] = []
-        new_bounding_box_dict[n_time+'_2'] = []
-        new_bounding_box_dict[n_time+'_3'] = []
+        new_bounding_box_dict[n_time + '_0'] = []
+        new_bounding_box_dict[n_time + '_1'] = []
+        new_bounding_box_dict[n_time + '_2'] = []
+        new_bounding_box_dict[n_time + '_3'] = []
         for k, val in enumerate(boundingBoxList):
             boundingBoxTop = merge_image_height * val['Top']
-            #找出当前边框属于哪张图片范围
+            # 找出当前边框属于哪张图片范围
             boxDict = {}
             for i in range(image_size['num']):
-                min = i*single_height       #第n张图
-                max = (i+1)*single_height
+                min = i * single_height  # 第n张图
+                max = (i + 1) * single_height
                 if boundingBoxTop >= min and boundingBoxTop <= max:
                     # print("属于第{i}张图".format(i=i+1))
                     boxDict['Width'] = val['Width']
-                    boxDict['Height'] = merge_image_height*val['Height']/single_height
-                    boxDict['Top'] = ((merge_image_height*val['Top'])-(i*single_height))/single_height #减去前i张图片的高度
+                    boxDict['Height'] = merge_image_height * val['Height'] / single_height
+                    boxDict['Top'] = ((merge_image_height * val['Top']) - (
+                                i * single_height)) / single_height  # 减去前i张图片的高度
                     boxDict['Left'] = val['Left']
-                    boxDict['picName'] = "{n_time}_{i}".format(n_time=n_time,i=i)
+                    boxDict['picName'] = "{n_time}_{i}".format(n_time=n_time, i=i)
                     # new_bounding_box_list.append(boxDict)
                     # new_bounding_box_list.append(boxDict)
-                    new_bounding_box_dict["{n_time}_{i}".format(n_time=n_time,i=i)].append(boxDict)
+                    new_bounding_box_dict["{n_time}_{i}".format(n_time=n_time, i=i)].append(boxDict)
         # exit(new_bounding_box_list)
         user_labels_list = list(new_labels_type.keys())
         user_labels_list.sort()
         return {'label_type': user_labels_list, 'label_list': label_result_list,
-                'new_bounding_box_dict':new_bounding_box_dict}
-
+                'new_bounding_box_dict': new_bounding_box_dict}
 
     def upload_s3(self, file_path, upload_path):
         try:
-            aws_key = "AKIA2E67UIMD45Y3HL53" #【你的 aws_access_key】
-            aws_secret = "ckYLg4Lo9ZXJIcJEAKkzf2rWvs8Xth1FCjqiAqUw" # 【你的 aws_secret_key】
+            aws_key = "AKIA2E67UIMD45Y3HL53"  # 【你的 aws_access_key】
+            aws_secret = "ckYLg4Lo9ZXJIcJEAKkzf2rWvs8Xth1FCjqiAqUw"  # 【你的 aws_secret_key】
             session = Session(aws_access_key_id=aws_key,
                               aws_secret_access_key=aws_secret,
                               region_name="us-east-1")
             s3 = session.resource("s3")
             # client = session.client("s3")
-            bucket = "foreignpush" # 【你 bucket 的名字】 # 首先需要保.证 s3 上已经存在该存储桶,否则报错
+            bucket = "foreignpush"  # 【你 bucket 的名字】 # 首先需要保.证 s3 上已经存在该存储桶,否则报错
             upload_data = open(file_path, "rb")
             # upload_key = "test"
             s3.Bucket(bucket).put_object(Key=upload_path, Body=upload_data)
@@ -768,7 +857,7 @@ class AiView(View):
             return nickname
 
     def get_msg_text(self, channel, n_time, lang, tz, label_list):
-        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz,lang=lang)
+        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
         if lang == 'cn':
             msg = '摄像头AI识别到了{}'.format(label_list)
             send_text = '{msg} 通道:{channel} 日期:{date}'.format(msg=msg, channel=channel, date=n_date)
@@ -815,7 +904,6 @@ class AiView(View):
         except Exception as e:
             return 'serverKey abnormal'
 
-
     def do_apns(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
         logger = logging.getLogger('info')
         logger.info("进来do_apns函数了")
@@ -823,7 +911,8 @@ class AiView(View):
         logger.info(APNS_MODE)
         logger.info(os.path.join(BASE_DIR, APNS_CONFIG[appBundleId]['pem_path']))
         try:
-            cli = apns2.APNSClient(mode=APNS_MODE, client_cert=os.path.join(BASE_DIR, APNS_CONFIG[appBundleId]['pem_path']))
+            cli = apns2.APNSClient(mode=APNS_MODE,
+                                   client_cert=os.path.join(BASE_DIR, APNS_CONFIG[appBundleId]['pem_path']))
             push_data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
                          "received_at": n_time, "sound": "", "uid": uid, "zpush": "1", "channel": channel}
             alert = apns2.PayloadAlert(body=msg_text, title=msg_title)
@@ -854,7 +943,7 @@ class AiView(View):
         eventType = request_dict.get('eventType', None)
 
         now_time = int(time.time())
-        seven_days_ago = now_time - 7 * 24 * 3600   # 查询7天内的数据
+        seven_days_ago = now_time - 7 * 24 * 3600  # 查询7天内的数据
         qs = Ai_Push_Info.objects.filter(userID_id=userID, eventTime__gt=seven_days_ago).order_by('-eventTime')
 
         if startTime and endTime:
@@ -903,17 +992,17 @@ class AiView(View):
                 s3_img_cover = '{uid}/{channel}/cover{time}.jpg'.format(uid=devUid, channel=channel, time=eventTime)
                 s3_img_desc = '{uid}/{channel}/desc{time}.jpg'.format(uid=devUid, channel=channel, time=eventTime)
                 response_url_cover = aws_s3_client.generate_presigned_url('get_object',
-                                                                    ExpiresIn=300,
-                                                                    Params={
-                                                                        'Bucket': 'aipush', 'Key': s3_img_cover
-                                                                    },
-                                                                    )
-                response_url_desc = aws_s3_client.generate_presigned_url('get_object',
                                                                           ExpiresIn=300,
                                                                           Params={
-                                                                              'Bucket': 'aipush', 'Key': s3_img_desc
+                                                                              'Bucket': 'aipush', 'Key': s3_img_cover
                                                                           },
                                                                           )
+                response_url_desc = aws_s3_client.generate_presigned_url('get_object',
+                                                                         ExpiresIn=300,
+                                                                         Params={
+                                                                             'Bucket': 'aipush', 'Key': s3_img_desc
+                                                                         },
+                                                                         )
                 p['img'] = response_url_cover
                 p['img_list'] = [response_url_desc]
 
@@ -923,13 +1012,14 @@ class AiView(View):
                 # 列表装载回放时间戳标记
                 p['img_list'] = []
                 for i in range(4):
-                    thumbspng = '{uid}/{channel}/{time}_{st}.jpg'.format(uid=devUid, channel=p['Channel'], time=eventTime, st=i)
+                    thumbspng = '{uid}/{channel}/{time}_{st}.jpg'.format(uid=devUid, channel=p['Channel'],
+                                                                         time=eventTime, st=i)
                     response_url = aws_s3_client.generate_presigned_url('get_object',
-                                                                             ExpiresIn=300,
-                                                                             Params={
-                                                                                 'Bucket': 'aipush', 'Key': thumbspng
-                                                                             },
-                                                                             )
+                                                                        ExpiresIn=300,
+                                                                        Params={
+                                                                            'Bucket': 'aipush', 'Key': thumbspng
+                                                                        },
+                                                                        )
                     p['img_list'].append(response_url)
 
             if devUid in uid_type_dict.keys():
@@ -944,18 +1034,18 @@ class AiView(View):
         is_update_all = request_dict.get('is_update_all', 0)
 
         try:
-            if int(is_update_all) == 1:     # 全部已读
+            if int(is_update_all) == 1:  # 全部已读
                 is_update = Ai_Push_Info.objects.filter(userID_id=userID).update(status=1)
                 return response.json(0, {'update_count': is_update})
             else:
                 id_list = request_dict.get('id_list', None)
                 if not id_list:
-                    request_dict.getlist('id_list[]', None)     # 获取IOS数组传参
+                    request_dict.getlist('id_list[]', None)  # 获取IOS数组传参
                 logger = logging.getLogger('info')
                 logger.info('已读ai消息id_list:{}'.format(id_list))
                 if not id_list:
                     return response.json(444)
-                id_list = eval(id_list)     # 字符串转列表
+                id_list = eval(id_list)  # 字符串转列表
                 param_flag = CommonService.get_param_flag(data=id_list)
                 if not param_flag:
                     return response.json(444)
@@ -995,5 +1085,3 @@ class AiView(View):
         except Exception as e:
             print(e)
             return response.json(500, repr(e))
-
-

+ 62 - 10
Controller/AppSetController.py

@@ -12,13 +12,15 @@
 @Contact: chanjunkai@163.com
 """
 from Ansjer.config import SERVER_TYPE
-from Model.models import AppSetModel,PromotionRuleModel
+from Model.models import AppSetModel, PromotionRuleModel, PopupsConfig, RedDotsConfig
 from django.views.generic.base import View
 from Object.RedisObject import RedisObject
 from Object.TokenObject import TokenObject
 from Service.ModelService import ModelService
-import time,json
+import time, json
 from Object.ResponseObject import ResponseObject
+
+
 class AppSetView(View):
     def get(self, request, *args, **kwargs):
         request.encoding = 'utf-8'
@@ -51,7 +53,14 @@ class AppSetView(View):
             else:
                 return response.json(tko.code)
         else:
-            return response.json(414)
+            token = request_dict.get('token', None)
+            tko = TokenObject(token)
+            if tko.code == 0:
+                userID = tko.userID
+                if operation == 'page_set':  # app弹窗标记红点设置
+                    return self.do_page_set(userID, request_dict, response)
+            else:
+                return response.json(tko.code)
 
     # 查询
     def do_query(self, request_dict, response):
@@ -67,12 +76,13 @@ class AppSetView(View):
             if not app_set_qs[0]['content']:
                 return response.json(0)
             dict_json = json.loads(app_set_qs[0]['content'])
-            #加入促销弹窗
-            promotion = PromotionRuleModel.objects.filter(status=1).values('startTime','endTime','popups')
+
+            # 加入促销弹窗
+            promotion = PromotionRuleModel.objects.filter(status=1).values('startTime', 'endTime', 'popups')
             if promotion.exists():
                 dict_json['popupsStartTime'] = promotion[0]['startTime']
                 dict_json['popupsEndTime'] = promotion[0]['endTime']
-                dict_json['popupsContent'] = json.loads(promotion[0]['popups']).get(lang,'')
+                dict_json['popupsContent'] = json.loads(promotion[0]['popups']).get(lang, '')
                 dict_json['nowTime'] = int(time.time())
             if 'editionUpgrading' in dict_json:
                 if dict_json['editionUpgrading'] == 1:
@@ -82,9 +92,11 @@ class AppSetView(View):
                         dict_json['editionUpgrading'] = 'Upgrading, please sign in later'
                 else:
                     dict_json['editionUpgrading'] = ''
+
             return response.json(0, dict_json)
         except Exception as e:
-            return response.json(500, repr(e))
+            return response.json(500, "错误行数:{errLine}, 错误信息: {errmsg}".format(errLine=e.__traceback__.tb_lineno,
+                                                                              errmsg=repr(e)))
 
         # res = {}
         # res['grade'] = 1
@@ -115,7 +127,7 @@ class AppSetView(View):
         sm_qs = AppSetModel.objects.filter(appBundleId=appBundleId)
         count = sm_qs.count()
         nowTime = int(time.time())
-        if count>0:
+        if count > 0:
             sm_qs = sm_qs.values('id', 'appBundleId', 'content', 'addTime', 'updTime')
             return response.json(0, {'data': list(sm_qs), 'count': count})
         else:
@@ -137,7 +149,7 @@ class AppSetView(View):
         sm_qs = AppSetModel.objects.filter(appBundleId=appBundleId)
         redis = RedisObject()
         if SERVER_TYPE != "Ansjer.formal_settings":
-            key_id= "www"+appBundleId
+            key_id = "www" + appBundleId
         else:
             key_id = "test" + appBundleId
         redis.del_data(key=key_id)
@@ -145,4 +157,44 @@ class AppSetView(View):
             sm_qs.update(content=content, updTime=nowTime)
             return response.json(0)
         else:
-            return response.json(173)
+            return response.json(173)
+
+    def do_page_set(self, userID, request_dict, response):
+        lang = request_dict.get('lang', 'en')
+        dict_json = {}
+        now_time = int(time.time())
+        dict_json['popups'] = {
+            'title': '',
+            'content': '',
+            'status': 0,
+            'tag': 1,
+        }
+        #弹窗
+        popups_obj = PopupsConfig.objects.filter(lang=lang).values('title','content','start_time','end_time','tag')
+        if popups_obj.exists:
+            popups_status = 0
+            if now_time >= popups_obj[0]['start_time'] and now_time <= popups_obj[0]['end_time']:
+                popups_status = 1
+            dict_json['popups'] = {
+                'title': popups_obj[0]['title'],
+                'content': popups_obj[0]['content'],
+                'status': popups_status,
+                'tag': popups_obj[0]['tag'],
+            }
+
+        #红点标记
+        dict_json['red_dots'] = []
+        red_dots_obj = RedDotsConfig.objects.values('module','start_time','end_time')
+        for red_dots in red_dots_obj:
+            red_dots_status = 0
+            if now_time >= red_dots['start_time'] and now_time <= red_dots['end_time']:
+                red_dots_status = 1
+            dict_json['red_dots'].append({
+                'module': red_dots['module'],
+                'status': red_dots_status,
+            })
+
+
+
+        dict_json['red_dots'] = list(dict_json['red_dots'])
+        return response.json(0, dict_json)

+ 31 - 190
Controller/CloudStorage.py

@@ -42,7 +42,8 @@ from Ansjer.config import OSS_STS_ACCESS_KEY, OSS_STS_ACCESS_SECRET, OSS_ROLE_AR
 from Controller.CheckUserData import DataValid
 from Model.models import Device_Info, Order_Model, Store_Meal, VodHlsModel, OssCrdModel, UID_Bucket, StsCrdModel, \
     ExperienceContextModel, Pay_Type, CDKcontextModel, Device_User, SysMassModel, SysMsgModel, UidPushModel, \
-    Unused_Uid_Meal, UIDMainUser, UserModel, PromotionRuleModel, VideoPlaybackTimeModel, CloudLogModel, CouponModel, VodBucketModel
+    Unused_Uid_Meal, UIDMainUser, UserModel, PromotionRuleModel, VideoPlaybackTimeModel, CloudLogModel, CouponModel, \
+    VodBucketModel, UIDModel
 from Object.AWS.S3Email import S3Email
 from Object.AliPayObject import AliPayObject
 from Object.AliSmsObject import AliSmsObject
@@ -60,6 +61,7 @@ from Ansjer.config import SERVER_TYPE
 from Service.ModelService import ModelService
 
 # SERVER_DOMAIN = 'http://test.dvema.com/'
+from Service.PayService import PaymentService
 
 '''
 生成订单
@@ -625,184 +627,15 @@ class CloudStorageView(View):
 
     def do_pay_error(self):
         response = HttpResponse()
-        response.content = '''
-<!DOCTYPE html>
-<html>
-<head>
-	<!--浏览器不缓存-->
-	<meta http-equiv="Pragma" content="no-cache">
-	<meta http-equiv="Cache-Control" content="no-cache">
-	<meta http-equiv="Expires" content="0">
-	<!--utf-8-->
-    <meta http-equiv="content-type" content="text/html;charset=utf-8">
-    <!-- viewport的<meta>标签,这个标签可以修改在大部分的移动设备上面的显示,为了确保适当的绘制和触屏缩放。-->
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <link rel="shortcut icon" href="https://test.zositechc.cn/web/images/favicon.ico" type="image/x-icon"  charset="utf-8"/>  
-    <title>Trading particulars</title>
-    <style>
-    	.title_head{
-    		height: 50px;
-    		border-radius: 5px;
-    		background-color: #c3c6c7; 
-    		text-align: center;
-    		line-height: 50px;
-    	}
-    	.content{
-    		text-align: center;
-    		margin-top: 50px;
-    		font-size: 20px;
-    		color : #ec7648
-    	}
-    	.content_img{
-    		width: 60px; 
-    		height: 60px;
-    	}
-    	.bottom{
-    		 margin-bottom: 10px; 
-    		 margin-top: 250px; 
-    		 color : #ec7648
-    	}
-    	.bottom_div{
-    		border: 1px solid #ec7648; 
-    		line-height: 38px; 
-    		text-align: center; 
-    		width: 100px; 
-    		height: 38px;
-    		border-radius: 5px;
-    	}
-    	
-    	.bottom_div:hover{
-    		background-color: #dde4e2;
-    	}
-    </style>
-</head>
-<body>
-	<div class="title_head">Trading particulars</div>
-    <div class="content">
-    	<p >
-    		<img src="https://test.zositechc.cn/web/images/failed.jpg" class="content_img">
-    		<br />
-    		Payment failure
-    	</p>
-    </div>
-    <center class="bottom">
-    	<div class="bottom_div" onclick="payOKButton()"> 
-    	 Finish
-    	</div>
-    </center>
-    <script> 	    // 点击付款成功按钮
-    function payOKButton() {
-        // 复杂数据
-        console.log('success')
-        window.location.href="https://www.baidu.com?page=closePage";
-    }
-	</script>
-</body> 
-</html>
-                '''
+        response.content = PaymentService.get_pay_error_content()
         return response
 
     def do_pay_ok(self, request_dict):  # 支付成功
         response = HttpResponse()
-        paytype = request_dict.get('paytype', None)
         lang = request_dict.get('lang', 'en')
+        pay_type = request_dict.get('paytype', None)
 
-        showtitle = "支付成功"
-        if paytype == "10" :
-            showtitle = "成功体验云存"
-
-        if paytype == "11":
-            showtitle = "兑换成功"
-
-        wancheng = '完成'
-        if lang != 'cn':
-            showtitle = "Payment successful"
-            if paytype == "10":
-                showtitle = "Successful experience of cloud storage"
-
-            if paytype == "11":
-                showtitle = "Successful exchange"
-
-            wancheng = 'complete'
-        response.content = '''
-        
-<html>
-<head>
-        <!--浏览器不缓存-->
-        <meta http-equiv="Pragma" content="no-cache">
-        <meta http-equiv="Cache-Control" content="no-cache">
-        <meta http-equiv="Expires" content="0">
-        <!--utf-8-->
-    <meta http-equiv="content-type" content="text/html;charset=utf-8">
-    <!-- viewport的<meta>标签,这个标签可以修改在大部分的移动设备上面的显示,为了确保适当的绘制和触屏缩放。-->
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <link rel="shortcut icon" href="https://test.zositechc.cn/web/images/favicon.ico" type="image/x-icon" charset="utf-8">  
-    <title>''' + showtitle + '''</title>
-    <style>
-            .title_head{
-                    height: 50px;
-                    border-radius: 5px;
-                    background-color: #c3c6c7; 
-                    text-align: center;
-                    line-height: 50px;
-            }
-            .content{
-                    text-align: center;
-                    margin-top: 50px;
-                    font-size: 15px;
-                    color:#0000008A;
-					
-            }
-            .content_img{
-					margin-bottom:15px;
-                    width: 60px; 
-                    height: 60px;
-            }
-            .bottom{
-                     margin-bottom: 10px; 
-                     margin-top: 250px; 
-                     color : white;
-            }
-            .bottom_div{
-                    border: 1px solid #68c9c5; 
-                    line-height: 38px; 
-                    text-align: center; 
-                    width: 100px; 
-                    height: 38px;
-                    border-radius: 30px;
-					background-color:#68c9c5; 
-            }
-            
-            .bottom_div:hover{
-                    background-color: #dde4e2;
-            }
-    </style>
-</head>
-<body style="" rlt="1" inmaintabuse="true">
-        
-    <div class="content">
-            <p>
-					<img src="https://test.zositechc.cn/web/images/success.png" class="content_img">
-                    <br>
-                    ''' + showtitle + '''
-            </p>
-    </div>
-    <center class="bottom">
-            <div class="bottom_div" onclick="payOKButton()"> 
-             '''+wancheng+'''
-            </div>
-    </center>
-    <script src="//hm.baidu.com/hm.js?eaa57ca47dacb4ad4f5a257001a3457c"></script><script>             // 点击付款成功按钮
-    function payOKButton() {
-        // 复杂数据
-        console.log('success')
-        window.location.href="https://www.baidu.com?page=closePage"  
-    }
-        </script>
- 
-
-        <div id="qds" style="display:none;"></div></body></html>
-        '''
+        response.content = PaymentService.get_pay_ok_content(lang, pay_type)
         return response
 
     def do_pay_by_ali_callback(self, request):  # 阿里支付回调
@@ -902,7 +735,7 @@ class CloudStorageView(View):
 
                     # 核销coupon
                     if order_list[0]['coupon_id']:
-                        CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2)
+                        CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2, update_time=nowTime)
 
                     order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id, promotion_rule_id=promotion_rule_id)
                     datetime = time.strftime("%Y-%m-%d", time.localtime())
@@ -1052,7 +885,7 @@ class CloudStorageView(View):
 
                 # 核销coupon
                 if order_list[0]['coupon_id']:
-                    CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2)
+                    CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2, update_time=nowTime)
 
                 order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id, promotion_rule_id=promotion_rule_id)
                 datetime = time.strftime("%Y-%m-%d", time.localtime())
@@ -1182,7 +1015,7 @@ class CloudStorageView(View):
 
                     # 核销coupon
                     if order_list[0]['coupon_id']:
-                        CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2)
+                        CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2, update_time=nowTime)
 
                     order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id, promotion_rule_id=promotion_rule_id)
                     datetime = time.strftime("%Y-%m-%d", time.localtime())
@@ -1271,16 +1104,16 @@ class CloudStorageView(View):
         orderID = CommonService.createOrderID()
         #优惠券
         if coupon_id:
-            couponObj = CouponModel.objects.filter(id=coupon_id, use_status=0, distributeTime__lte=nowTime,
+            couponObj = CouponModel.objects.filter(id=coupon_id, use_status=0, distribute_time__lte=nowTime,
                                                    valid_time__gt=nowTime)
-            couponQuery = couponObj.values("id", "type", "coupon_discount")
+            couponQuery = couponObj.values("id", "coupon_config__type", "coupon_config__coupon_discount")
             if not couponQuery.exists():
                 return response.json(10049)
             price = Decimal(price)
-            coupon_discount = Decimal(couponQuery[0]['coupon_discount'])
-            if couponQuery[0]['type'] == 1:  #打折
+            coupon_discount = Decimal(couponQuery[0]['coupon_config__coupon_discount'])
+            if couponQuery[0]['coupon_config__type'] == 1:  #打折
                 price = coupon_discount/10 * price
-            elif couponQuery[0]['type'] == 2:  #抵扣
+            elif couponQuery[0]['coupon_config__type'] == 2:  #抵扣
                 price = price - coupon_discount
         price = float(price)
         if price < 0 or price == 0 or price < 0.01:
@@ -1305,7 +1138,7 @@ class CloudStorageView(View):
                                            rank_id=rank, plan_id=subInfo['plan_id'],coupon_id=coupon_id,ai_rank_id=1)
                 # if coupon_id:
                 #     #冻结优惠券
-                #     CouponModel.objects.filter(id=coupon_id, use_status=0, distributeTime__lte=nowTime,
+                #     CouponModel.objects.filter(id=coupon_id, use_status=0, distribute_time__lte=nowTime,
                 #                                            valid_time__gt=nowTime).update(use_status=1)
 
                 return response.json(0, {"redirectUrl": subInfo['url'], "orderID": orderID})
@@ -1620,7 +1453,7 @@ class CloudStorageView(View):
         uid = request_dict.get('uid', None)
         lang = request_dict.get('lang', 'en')
         dv_qs = Device_Info.objects.filter(userID_id=userID, UID=uid, isShare=False, isExist=1) \
-            .values('vodPrimaryUserID')
+            .values('vodPrimaryUserID', 'serial_number', 'Type')
         if not dv_qs.exists() or dv_qs[0]['vodPrimaryUserID'] != userID:
             return response.json(12)
         now_time = int(time.time())
@@ -1630,6 +1463,14 @@ class CloudStorageView(View):
                                                                     "use_status","endTime","has_unused","bucket__id")
         if not uid_bucket:
             return response.json(10030)
+
+        # 如果存在序列号返回完整序列号
+        device_id = uid
+        serial_number = dv_qs[0]['serial_number']
+        if serial_number:
+            device_id = CommonService.get_full_serial_number(uid, serial_number, dv_qs[0]['Type'])
+
+        uid_bucket[0]['uid'] = device_id
         uid_bucket[0]['storage'] = 0
         has_unused = uid_bucket[0]['has_unused']
         del uid_bucket[0]['has_unused']
@@ -1645,13 +1486,13 @@ class CloudStorageView(View):
                     month += 's'
                 storage = "{storage_time}{month}".format(storage_time=storage_time, month=month)
                 unused_dict = {
-                    "id":ub['unused_id'],
-                    "uid":ub['uid'],
-                    "bucket__content":ub['bucket__content'],
-                    "use_status":0,
-                    "endTime":0,
-                    "bucket__id":ub['bucket__id'],
-                    "storage":storage,
+                    "id": ub['unused_id'],
+                    "uid": device_id,
+                    "bucket__content": ub['bucket__content'],
+                    "use_status": 0,
+                    "endTime": 0,
+                    "bucket__id": ub['bucket__id'],
+                    "storage": storage,
                 }
                 store_list.append(unused_dict)
                 bucket_id_list.append(ub['bucket__id'])

+ 59 - 38
Controller/CouponController.py

@@ -6,7 +6,7 @@ import os
 import time
 import math
 from django.views.generic.base import View
-from Model.models import CouponModel,Device_User
+from Model.models import CouponModel, Device_User, CouponConfigModel, CouponLang
 from Object.ResponseObject import ResponseObject
 from Object.TokenObject import TokenObject
 from django.db.models import Q, F, Count
@@ -41,59 +41,80 @@ class CouponView(View):
             if tko.code != 0:
                 return response.json(tko.code)
             userID = tko.userID
-            if operation == 'UserCoupon':  #用户优惠券
+            if operation == 'UserCoupon':  # 用户优惠券
                 return self.query_user_coupon(request_dict, userID, response)
             else:
                 return response.json(414)
 
-    def generate_coupon(self,request_dict,response):
+    def generate_coupon(self, request_dict, response):
         username = request_dict.get('username', None)
         num = request_dict.get('num', None)
         userID = Device_User.objects.filter(username=username).values('userID')[0]['userID']
+        coupon_config_id = request_dict.get('coupon_config_id', 1)
 
         try:
             data = []
-            for i in range(int(num)):
-                if i % 2 == 0:
-                    data.append(CouponModel(
-                        userID_id=userID,
-                        use_status=0,
-                        type=1,
-                        coupon_discount=7,
-                        distributeTime=int(time.time()),
-                        valid_time=int(time.time()) + 8640000,
-                        addTime=int(time.time())
-                    ))
-                else:
-                    data.append(CouponModel(
-                        userID_id=userID,
-                        use_status=0,
-                        type=2,
-                        coupon_discount=0.01,
-                        distributeTime=int(time.time()),
-                        valid_time=int(time.time()) + 8640000,
-                        addTime=int(time.time())
-                    ))
-            CouponModel.objects.bulk_create(data)
+            # CouponConfigModel.objects.create(type=1, use_range=1, coupon_discount=8)
+            # CouponLangObj = CouponLang.objects.create(
+            #     lang='cn',
+            #     instruction='用于自动续费套餐的首月',
+            #     quota='八',
+            #     unit='折',
+            #     remark='每台摄像机上只能用一次'
+            # )
+            # CouponConfigModel.objects.get(id=1).lang.add(CouponLangObj.id)
+            now_time = int(time.time())
+            CouponModel.objects.create(
+                use_status=0,
+                distribute_time=now_time,
+                valid_time=now_time+10000000,
+                userID=userID,
+                coupon_config_id=coupon_config_id,
+                update_time=now_time,
+                create_time=now_time
+            )
             return HttpResponse('success')
         except Exception as e:
-            return HttpResponse(repr(e))
+            return HttpResponse(
+                "错误行数:{errLine}, 错误信息: {errmsg}".format(errLine=e.__traceback__.tb_lineno, errmsg=repr(e)))
 
+    def query_user_coupon(self, request_dict, userID, response):  # 用户优惠券列表
+        now_time = int(time.time())
+        lang = request_dict.get('lang', 'en')
+        # couponObj = CouponModel.objects.filter(userID_id=userID, use_status=0, distributeTime__lte=now_time,
+        #                                        valid_time__gt=now_time).annotate(coupon_id=F('id')).values(
+        #     "coupon_id", "type", "coupon_discount", "valid_time")
 
+        coupon_obj = CouponModel.objects.filter(
+            userID=userID,
+            use_status=0,
+            distribute_time__lte=now_time,
+            valid_time__gt=now_time,
+            coupon_config__lang__lang=lang,
+        ).annotate(
+            coupon_id=F('id'),
+            type=F('coupon_config__type'),
+            coupon_discount=F('coupon_config__coupon_discount'),
+            instruction=F('coupon_config__lang__instruction'),
+            remark=F('coupon_config__lang__remark'),
+            quota=F('coupon_config__lang__quota'),
+            unit=F('coupon_config__lang__unit'),
+        ).values(
+            "coupon_id",
+            "type",
+            "coupon_discount",
+            "valid_time",
+            "instruction",
+            "remark",
+            "quota",
+            "unit",
+        )
 
+        for couponList in coupon_obj:
+            couponList['valid_time'] = CommonService.timestamp_to_str(couponList['valid_time'])
 
-
-
-
-    def query_user_coupon(self, request_dict, userID, response):  #用户优惠券列表
-        nowTime = int(time.time())
-        couponObj = CouponModel.objects.filter(userID_id=userID,use_status=0,distributeTime__lte=nowTime,
-                                               valid_time__gt=nowTime).annotate(coupon_id=F('id')).values(
-                                               "coupon_id","type","coupon_discount","valid_time")
-        for couponList in couponObj:
-            couponList['valid_time'] =  CommonService.timestamp_to_str(couponList['valid_time'])
         result = {
-            'count':couponObj.count(),
-            'couponList':list(couponObj),
+            'count': coupon_obj.count(),
+            'couponList': list(coupon_obj),
         }
         return response.json(0, result)

+ 21 - 6
Controller/DetectControllerV2.py

@@ -55,10 +55,14 @@ class DetectControllerViewV2(View):
         return self.validation(request.POST, operation)
 
     def validation(self, request_dict, operation):
+
         response = ResponseObject()
         if operation is None:
             return response.json(444, 'error path')
         token = request_dict.get('token', None)
+        lang = request_dict.get('lang', None)
+        if lang:
+            response = ResponseObject(lang)
         tko = TokenObject(token)
         if tko.code == 0:
             userID = tko.userID
@@ -132,7 +136,9 @@ class DetectControllerViewV2(View):
             # 判断用户是否拥有设备
             device_info_qs = Device_Info.objects.filter(userID_id=userID, UID=uid)
             if not device_info_qs.exists():
-                return response.json(14)
+                device_info_qs = Device_Info.objects.filter(userID_id=userID, serial_number=uid)
+                if not device_info_qs.exists():
+                    return response.json(14)
 
             # 更新或创建uid_set数据
             nowTime = int(time.time())
@@ -147,7 +153,9 @@ class DetectControllerViewV2(View):
             # 检测类型
             if detect_group:
                 uid_set_data['detect_group'] = detect_group
-
+            uid_set = UidSetModel.objects.filter(uid=uid).order_by('-updTime')
+            if uid_set.exists():
+                interval = uid_set.first().new_detect_interval if not interval else interval
             # 设置消息推送间隔
             if interval:
                 interval = int(interval)
@@ -162,9 +170,10 @@ class DetectControllerViewV2(View):
                             'IntervalTime': interval
                         }
                     }
-                    req_success = CommonService.req_publish_mqtt_msg(uid, topic_name, msg)
-                    if not req_success:
-                        return response.json(10044)
+                    CommonService.req_publish_mqtt_msg(uid, topic_name, msg)
+                    # req_success = CommonService.req_publish_mqtt_msg(uid, topic_name, msg)
+                    # if not req_success:
+                    #     return response.json(10044)
 
             uid_set_qs = UidSetModel.objects.filter(uid=uid)
             if uid_set_qs.exists():
@@ -351,12 +360,13 @@ class DetectControllerViewV2(View):
             region_name='us-east-1'
         )
         # vod_time_list = []
+        # ai消息标识所有组合标签
+        ai_all_event_type = EquipmentInfoService.get_all_comb_event_type()
         for p in qr:
             devUid = p['devUid']
             eventTime = p['eventTime']
             channel = p['Channel']
             storage_location = p['storage_location']
-            p['borderCoords'] = '' if p['borderCoords'] == '' else json.loads(p['borderCoords'])
             if p['is_st'] == 1:
                 thumbspng = '{uid}/{channel}/{time}.jpeg'.format(uid=devUid, channel=p['Channel'], time=eventTime)
                 if storage_location == 1:  # oss
@@ -433,6 +443,11 @@ class DetectControllerViewV2(View):
                 p['devNickName'] = uid_type_dict[devUid]['NickName']
             else:
                 p['uid_type'] = ''
+
+            p['borderCoords'] = '' if p['borderCoords'] == '' else json.loads(p['borderCoords'])  # ai消息坐标信息
+            p['ai_event_type_list'] = []
+            if p['eventType'] in ai_all_event_type:  # 如果是ai消息类型,则分解eventType, 如:123 -> [1,2,3]
+                p['ai_event_type_list'] = list(map(int, str(p['eventType'])))
             res.append(p)
         return response.json(0, {'datas': res, 'count': count})
 

+ 31 - 31
Controller/DeviceConfirmRegion.py

@@ -1,10 +1,11 @@
+import logging
 import time
 
 from django.utils.decorators import method_decorator
 
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic import TemplateView
-from Model.models import CountryModel, RegionModel, P2PIpModel, DeviceDomainModel
+from Model.models import CountryModel, RegionModel, P2PIpModel, DeviceDomainModel, DeviceDomainRegionModel
 from Object.ResponseObject import ResponseObject
 from Service.CommonService import CommonService
 
@@ -18,25 +19,29 @@ class ConfirmRegion(TemplateView):
     def get(self, request, *args, **kwargs):
         response = ResponseObject()
         request.encoding = 'utf-8'
-        # number = request.POST.get('number', None)
-        # selectRegion = CountryModel.objects.filter(number=number).values('region__api_url')
-        # if not selectRegion.exists():
-        #     return response.json(0,{"api_url":"https://test.dvema.com"})
-        # else:
-        #     return response.json(0,{"api_url":selectRegion[0]['region__api_url']})
-
-        # serial_number = request.POST.get('serial_number', None)
-        ip = CommonService.get_ip_address(request)
-        # ip = '14.102.176.0'
-        ipInfo = CommonService.getIpIpInfo(ip, "CN")
-        if ipInfo['country_code']:
-            device_request_url = CountryModel.objects.filter(country_code=ipInfo['country_code']).values("region__api")
-            if device_request_url.exists():
-                return response.json(0, {"request_api_url":device_request_url[0]['region__api']})
-
-        # 不存在默认返回美洲地区api。
-        api = RegionModel.objects.filter(continent_code='NA').values("api")
-        return response.json(0,{"request_api_url": api[0]['api']})
+        try:
+            ip = CommonService.get_ip_address(request)
+            device_domain_data = {'ip': ip}
+            ipInfo = CommonService.getIpIpInfo(ip, 'CN')
+            logger = logging.getLogger('info')
+            logger.info('设备获取域名---ip:{},country_code:{}'.format(ip, ipInfo['country_code']))
+            if ipInfo['country_code']:
+                device_domain_data['country_name'] = ipInfo['country_code']
+                device_request_url = CountryModel.objects.filter(country_code=ipInfo['country_code']).values("region__api")
+                if device_request_url.exists():
+                    device_domain_data['api'] = device_request_url[0]['region__api']
+                    DeviceDomainModel.objects.create(**device_domain_data)
+                    return response.json(0, {'request_api_url': device_request_url[0]['region__api']})
+
+            # 不存在默认返回美洲地区api。
+            api = RegionModel.objects.filter(continent_code='NA').values("api")
+            device_domain_data['country_name'] = 'NA'
+            device_domain_data['api'] = api[0]['api']
+            DeviceDomainModel.objects.create(**device_domain_data)
+            return response.json(0, {'request_api_url': api[0]['api']})
+        except Exception as e:
+            print(e)
+            return response.json(0, {'request_api_url': 'https://www.dvema.com'})
 
 
 # 根据设备的ip返回域名和地区id
@@ -47,17 +52,12 @@ class ConfirmRegionV2(TemplateView):
 
     def get(self, request, *args, **kwargs):
         response = ResponseObject()
-        uid = request.GET.get('uid', None)
         serial_number = request.GET.get('serial_number', None)
-        if not uid and not serial_number:
+        if not serial_number:
             return response.json(444)
         try:
-            if uid:
-                data_dict = {'uid': uid}
-                device_domain_qs = DeviceDomainModel.objects.filter(uid=uid)
-            else:
-                data_dict = {'serial_number': serial_number}
-                device_domain_qs = DeviceDomainModel.objects.filter(serial_number=serial_number)
+            data_dict = {'serial_number': serial_number}
+            device_domain_region_qs = DeviceDomainRegionModel.objects.filter(serial_number=serial_number)
 
             # 根据请求ip确认地区
             request.encoding = 'utf-8'
@@ -82,10 +82,10 @@ class ConfirmRegionV2(TemplateView):
             # 更新或创建设备域名数据
             data_dict['api'] = api
             data_dict['region_id'] = region_id
-            if device_domain_qs.exists():
-                device_domain_qs.update(**data_dict)
+            if device_domain_region_qs.exists():
+                device_domain_region_qs.update(**data_dict)
             else:
-                device_domain_qs.create(**data_dict)
+                device_domain_region_qs.create(**data_dict)
             res = {'request_api_url': api, 'region_id': region_id}
             return response.json(0, res)
         except Exception as e:

+ 23 - 1
Controller/EquipmentInfo.py

@@ -5,7 +5,7 @@ from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic.base import View
 
-from Model.models import Device_Info, Equipment_Info
+from Model.models import Device_Info, Equipment_Info, UidPushModel
 from Model.models import Device_User
 from Object.ResponseObject import ResponseObject
 from Object.TokenObject import TokenObject
@@ -93,6 +93,28 @@ class EquipmentInfo(View):
                 addTime=nowTime,
                 alarm=alarm)
             equipment_info.save()
+            uid_push_qs = UidPushModel.objects.filter(uid_set__uid=devUid, uid_set__detect_status=1). \
+                values('token_val', 'app_type', 'appBundleId', 'm_code', 'push_type', 'userID_id', 'userID__NickName',
+                       'lang', 'm_code', 'tz', 'uid_set__nickname', 'uid_set__detect_interval', 'uid_set__detect_group',
+                       'uid_set__channel')
+            tz = '0'
+            if uid_push_qs.exists():
+                tz = uid_push_qs.first().tz
+            local_date_time = CommonService.get_now_time_str(n_time=nowTime, tz=tz, lang='cn')
+            local_date_time = local_date_time[0:10]
+            equipment_info_vo = EquipmentInfoService.get_equipment_info_obj(
+                local_date_time,
+                device_user_id=userID,
+                event_time=nowTime,
+                event_type=eventType,
+                device_uid=devUid,
+                device_nick_name=device_info[0].NickName,
+                channel=Channel,
+                alarm=alarm,
+                receive_time=receiveTime,
+                add_time=nowTime,
+            )
+            equipment_info_vo.save()
         except Exception:
             errorInfo = traceback.format_exc()
             print(errorInfo)

+ 4 - 1
Controller/EquipmentManagerV2.py

@@ -9,7 +9,7 @@ from django.views.generic.base import View
 
 from Ansjer.config import OSS_STS_ACCESS_SECRET, OSS_STS_ACCESS_KEY
 from Model.models import Device_Info, UID_Bucket, UID_Preview, UidSetModel, UidPushModel, UidChannelSetModel, \
-    iotdeviceInfoModel
+    iotdeviceInfoModel, UIDModel
 from Object.ResponseObject import ResponseObject
 from Object.TokenObject import TokenObject
 from Service.CommonService import CommonService
@@ -325,6 +325,9 @@ class EquipmentManagerV2(View):
             uv_dict[us['uid']]['channels'] = channels
 
         for p in dvls:
+            # 如果存在序列号返回完整序列号
+            if p['serial_number']:
+                p['UID'] = CommonService.get_full_serial_number(p['UID'], p['serial_number'], p['Type'])
             # 新增IOT
             p['iot'] = []
             for iot in iotqs:

+ 24 - 54
Controller/IotCoreController.py

@@ -4,6 +4,7 @@ import hashlib
 import logging
 import time
 import uuid
+from collections import OrderedDict
 
 import requests
 from django.views import View
@@ -11,7 +12,7 @@ from django.views import View
 from Ansjer.config import AWS_IOT_GETS3_PULL_CHINA_ID, AWS_IOT_GETS3_PULL_CHINA_SECRET, \
     AWS_IOT_GETS3_PULL_FOREIGN_ID, AWS_IOT_GETS3_PULL_FOREIGN_SECRET, AWS_ARN, AWS_IOT_SES_ACCESS_CHINA_REGION, \
     AWS_IOT_SES_ACCESS_FOREIGN_REGION_ASIA, AWS_IOT_SES_ACCESS_FOREIGN_REGION_EUROPE, \
-    AWS_IOT_SES_ACCESS_FOREIGN_REGION_AMERICA
+    AWS_IOT_SES_ACCESS_FOREIGN_REGION_AMERICA, CONFIG_INFO, CONFIG_TEST, CONFIG_CN
 from Controller.DeviceConfirmRegion import Device_Region
 from Model.models import Device_Info, iotdeviceInfoModel, SerialNumberModel, UidSetModel
 from Object.IOTCore.IotObject import IOTClient
@@ -41,7 +42,7 @@ class IotCoreView(View):
         if operation == 'createKeysAndCertificate':
             return self.create_keys_and_certificate(request_dict, response, request)
         elif operation == 'requestPublishMessage':
-            return self.request_publish_message(request_dict, response, request)
+            return self.request_publish_message(request_dict, response)
         elif operation == 'getS3PullKey':
             return self.get_s3_pull_key(request_dict, response, request)
         elif operation == 'thingRegroup':
@@ -128,15 +129,12 @@ class IotCoreView(View):
                 }
                 return response.json(0, {'res': res})
             else:
-                # 根据地区id确定使用的boto3 client
                 region_id = request_dict.get('region_id', None)
-                if region_id:
-                    region_id = int(region_id)
-                else:
-                    ip = CommonService.get_ip_address(request)
-                    region_id = Device_Region().get_device_region(ip)
-                iotClient = IOTClient(region_id)
+                if not region_id:
+                    # 根据配置信息确定region_id
+                    region_id = CommonService.confirm_region_id(request)
 
+                iotClient = IOTClient(int(region_id))
                 # 拼接物品名
                 thingName = CommonService.get_thing_name(company_mark, thing_name_suffix)
                 thingGroup = device_version + '_' + language
@@ -230,7 +228,7 @@ class IotCoreView(View):
             print(e)
             return response.json(500, repr(e))
 
-    def clear_Iot_Cerm(self, userID, request_dict, response):
+    def clear_Iot_Cerm(self, request_dict, response):
         serial_number = request_dict.get('serial_number', None)
 
         if serial_number:
@@ -243,56 +241,28 @@ class IotCoreView(View):
         else:
             return response.json(444)
 
-    def request_publish_message(self, request_dict, response, request):
-        # Alexa请求IoT Core下发MQTT消息通知设备开始或停止推流,或唤醒设备
+    # Alexa请求IoT Core下发MQTT消息通知设备开始或停止推流,或唤醒设备
+    @staticmethod
+    def request_publish_message(request_dict, response):
         UID = request_dict.get('UID', None)
-        MSG = request_dict.get('MSG', None)
-
-        if not all([UID, MSG]):
+        rtsp = request_dict.get('rtsp', None)
+        enable = request_dict.get('enable', '1')
+        if not all([UID, rtsp]):
             return response.json(444)
 
         try:
-            # 获取设备的物品名后缀
-            device_info_qs = Device_Info.objects.filter(UID=UID).values('UID', 'serial_number')
-            if not device_info_qs.exists():
-                return response.json(10043)
-            uid = device_info_qs[0]['UID']
-            serial_number = device_info_qs[0]['serial_number']
-            # 如果device_info表的serial_number不为空,物品名为'Ansjer_Device_序列号'
-            thing_name_suffix = serial_number if serial_number != '' else uid
-            # 获取数据组织将要请求的url
-            iot = iotdeviceInfoModel.objects.filter(thing_name__contains=thing_name_suffix).values('thing_name',
-                                                                                                   'endpoint',
-                                                                                                   'token_iot_number')
-            if not iot.exists():
-                return response.json(10043)
-            thing_name = iot[0]['thing_name'][14:]  # IoT core上的物品名: Ansjer_Device_ + 序列号+企业编码/uid
-            endpoint = iot[0]['endpoint']
-            Token = iot[0]['token_iot_number']
-            # Token = '297a601b3925e04daab5a60280650e09'
-            topic_suffix = '_power_topic' if 'Turn' in MSG else '_rtsp_topic'
-            topic_name = thing_name + topic_suffix  # MQTT主题
-
-            # api doc: https://docs.aws.amazon.com/zh_cn/iot/latest/developerguide/http.html
-            # url: https://IoT_data_endpoint/topics/url_encoded_topic_name?qos=1
-            # post请求url来发布MQTT消息
-            url = 'https://{}/topics/{}'.format(endpoint, topic_name)
-            authorizer_name = 'Ansjer_Iot_Auth'
-            signature = CommonService.rsa_sign(Token)  # Token签名
-            headers = {'x-amz-customauthorizer-name': authorizer_name, 'Token': Token,
-                       'x-amz-customauthorizer-signature': signature}
-            params = {'command': MSG}
-            r = requests.post(url=url, headers=headers, json=params, timeout=2)
-            if r.status_code == 200:
-                res = r.json()
-                if res['message'] == 'OK':
-                    return response.json(0)
-                return response.json(10044)
-            else:
-                # print('发布失败')
+            thing_name = CommonService.query_serial_with_uid(UID)   # 存在序列号则为使用序列号作为物品名
+            topic_name = 'ansjer/generic/{}'.format(thing_name)
+            msg = OrderedDict(
+                [
+                    ('alexaRtspCommand', rtsp),
+                    ('enable', int(enable)),
+                ]
+            )
+            if not CommonService.req_publish_mqtt_msg(thing_name, topic_name, msg):
                 return response.json(10044)
+            return response.json(0)
         except Exception as e:
-            # print(e)
             return response.json(500, repr(e))
 
     def get_s3_pull_key(self, request_dict, response, request):

+ 119 - 0
Controller/MessagePush/EquipmentMessagePush.py

@@ -0,0 +1,119 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : EquipmentMessagePush.py
+@Time    : 2022/5/16 11:46
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+
+import logging
+import time
+
+from django.db import transaction
+from django.views.generic.base import View
+
+from Model.models import AiService, UidSetModel
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from Service.CommonService import CommonService
+
+
+# 设备消息推送视图
+class EquipmentMessagePushView(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 validation(self, request_dict, request, operation):
+        token = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
+        lang = request_dict.get('lang', None)
+        logger = logging.getLogger('info')
+        logger.info("传参语言{}".format(lang))
+        lang = lang if lang else token.lang
+        response = ResponseObject(lang)
+        if token.code != 0:
+            return response.json(token.code)
+        if operation is None:
+            return response.json(444, 'error path')
+        elif operation == 'update':
+            return self.do_change_status_all(request_dict, response)
+        elif operation == 'query':
+            return self.do_uid_set_query(request_dict, response)
+
+    @classmethod
+    def do_uid_set_query(cls, request_dict, response):
+
+        logger = logging.getLogger('info')
+        uid = request_dict.get('uid', None)
+        logger.info("----查询状态打印uid{}".format(uid))
+        if not uid:
+            return response.json(444, 'uid')
+        uid_set_qs = UidSetModel.objects.filter(uid=uid).order_by('-updTime')
+        if uid_set_qs.exists():
+            uid_set = uid_set_qs.first()
+            data = {
+                'status': uid_set.is_notification,
+                'interval': uid_set.new_detect_interval
+            }
+            return response.json(0, res=data)
+        return response.json(0)
+
+    @classmethod
+    def do_change_status_all(cls, request_dict, response):
+        logger = logging.getLogger('info')
+        status = request_dict.get('status', None)
+        uid = request_dict.get('uid', None)
+        interval = request_dict.get('interval', None)
+        if not status:
+            return response.json(444, 'status')
+        if not uid:
+            return response.json(444, 'uid')
+        try:
+            with transaction.atomic():
+                n_time = int(time.time())
+                status = int(status)
+                uid_set_qs = UidSetModel.objects.filter(uid=uid)
+                if not uid_set_qs.exists():
+                    qs_data = {
+                        'uid': uid,
+                        'addTime': n_time,
+                        'updTime': n_time,
+                    }
+                    if interval:
+                        qs_data['new_detect_interval'] = int(interval)
+                        qs_data['is_notification'] = status
+                    UidSetModel.objects.create(**qs_data)
+                if status == 1:
+                    if uid_set_qs.exists():
+                        qs_data = {
+                            'updTime': n_time,
+                        }
+                        if interval:
+                            qs_data['new_detect_interval'] = int(interval)
+                        qs_data['is_notification'] = status
+                        uid_set_qs.update(**qs_data)
+                if status == 0:
+                    ai_service_qs = AiService.objects.filter(uid=uid, use_status=1)
+                    if ai_service_qs.exists():
+                        qs_data = {'uid': uid, 'updTime': n_time, 'detect_status': status}
+                        ai_service_qs.update(**qs_data)
+                        topic_name = 'ansjer/generic/{}'.format(uid)
+                        # mqtt通知设备关闭AI识别功能
+                        msg = {'commandType': 'AIDisable'}
+                        req_success = CommonService.req_publish_mqtt_msg(uid, topic_name, msg)
+                        logger.info("推送>>>> mqtt回调:{}".format(req_success))
+                    if uid_set_qs.exists():
+                        uid_set_data = {'updTime': n_time, 'uid': uid, 'new_detect_interval': int(interval),
+                                        'detect_status': int(interval), 'is_notification': status}
+                        uid_set_qs.update(**uid_set_data)
+            return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(500, repr(e))

+ 3 - 1
Controller/OrderContrller.py

@@ -127,7 +127,9 @@ class OrderView(View):
                 if d['UID'] == did['UID']:
                     d['did'] = did['id']
                     d['Type'] = did['Type']
-                    d['serialNumber'] = did['serial_number']
+                    # 如果存在序列号返回完整序列号
+                    if did['serial_number']:
+                        d['UID'] = CommonService.get_full_serial_number(d['UID'], did['serial_number'], did['Type'])
                     data.append(d)
             d['rank__content'] = d['rank__lang__content']
             del d['rank__lang__content']

+ 5 - 1
Controller/PaymentCycle.py

@@ -197,6 +197,10 @@ class PaypalCycleNotify(View):
                          ))
             logger.info('billing_agreement_state')
             logger.info(state)
+            red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+            if lang != 'cn':
+                red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+            return HttpResponseRedirect(red_url)
         try:
             userid = order_list[0]['userID__userID']
             username = order_list[0]['userID__username']
@@ -269,7 +273,7 @@ class PaypalCycleNotify(View):
 
                 # 核销coupon
                 if order_list[0]['coupon_id']:
-                    CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2)
+                    CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2, update_time=nowTime)
 
                 order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id,
                                 promotion_rule_id=promotion_rule_id, agreement_id=agreement_id)

+ 948 - 0
Controller/SensorGateway/EquipmentFamilyController.py

@@ -0,0 +1,948 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : EquipmentFamilyController.py
+@Time    : 2022/5/13 15:50
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+
+import time
+
+import oss2
+from django.db import connection
+from django.db import transaction
+from django.db.models import Q, Count
+from django.views.generic.base import View
+
+from Ansjer.config import OSS_STS_ACCESS_SECRET, OSS_STS_ACCESS_KEY
+from Controller.DeviceConfirmRegion import Device_Region
+from Model.models import Device_Info, UID_Bucket, UID_Preview, UidSetModel, UidChannelSetModel, \
+    iotdeviceInfoModel, UIDModel, Device_User, UserFamily, FamilyMember, FamilyMemberPermission, \
+    FamilyRoomDevice, FamilyRoom, FamilyMemberJoin
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from Service.CommonService import CommonService
+
+
+# 家庭设备管理
+class EquipmentFamilyView(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 validation(self, request_dict, request, operation):
+
+        token = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
+        lang = request_dict.get('lang', None)
+        if lang:
+            response = ResponseObject(lang)
+        else:
+            response = ResponseObject(token.lang) if token.lang else ResponseObject()
+
+        if token.code != 0:
+            return response.json(token.code)
+        user_id = token.userID
+
+        # 手机端添加设备,查询,修改
+        if operation == 'add':
+            return self.do_save(user_id, request_dict, response, request)
+        # 分页获取未添加房间设备
+        elif operation == 'family-device-query':
+            return self.get_device_not_in_room(user_id, request_dict, response)
+        # 条件查询设备列表
+        elif operation == 'query':
+            return self.do_device_query(user_id, request_dict, response)
+        # 获取家庭列表
+        elif operation == 'family-list':
+            return self.get_family_list(user_id, request_dict, response)
+        # 查询所在家庭列表
+        elif operation == "where-family-list":
+            return self.who_family_list(user_id, response)
+        # 家庭保存
+        elif operation == 'family-save':
+            return self.family_save(user_id, request_dict, response)
+        # 家庭设置
+        elif operation == 'family-setting':
+            return self.get_family_setting(request_dict, response)
+        # 家庭成员删除
+        elif operation == 'member-del':
+            return self.family_member_del(user_id, request_dict, response)
+        # 获取房间列表
+        elif operation == 'room-list':
+            return self.get_family_room_list(request_dict, response)
+        # 房间保存
+        elif operation == 'room-save':
+            return self.room_save(request_dict, response)
+        # 权限列表
+        elif operation == 'permission-list':
+            return self.get_member_permission_list(user_id, request_dict, response)
+        # 成员权限修改
+        elif operation == 'permission-update':
+            return self.changes_member_permission(user_id, request_dict, response)
+        elif operation == 'family/del':
+            return self.family_family_del(user_id, request_dict, response)
+        else:
+            return response.json(414)
+
+    @classmethod
+    def family_family_del(cls, user_id, request_dict, response):
+        """
+        删除家庭
+        @param user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        if not family_id:
+            return response.json(444)
+        family_id = int(family_id)
+        permission = cls.get_member_permission_details(user_id, family_id)
+        if not permission or permission == '003':
+            return response.json(404)
+        user_family_qs = UserFamily.objects.filter(id=family_id, user_id=user_id)
+        if not user_family_qs.exists():
+            return response.json(173)
+        count = UserFamily.objects.filter(user_id=user_id).count()
+        if count <= 1:
+            return response.json(10057)
+        family_room_device_qs = FamilyRoomDevice.objects.filter(family_id=family_id)
+        if family_room_device_qs.exists():
+            family_room_device_qs.delete()
+        family_join_qs = FamilyMemberJoin.objects.filter(family_id=family_id)
+        if family_join_qs.exists():
+            family_join_qs.delete()
+        family_room_qs = FamilyRoom.objects.filter(family_id=family_id)
+        if family_room_qs.exists():
+            family_room_qs.delete()
+        family_member_qs = FamilyMember.objects.filter(family_id=family_id)
+        if family_member_qs.exists():
+            family_member_qs.delete()
+        family_qs = UserFamily.objects.filter(id=family_id)
+        if family_qs.exists():
+            family_qs.delete()
+        return response.json(0)
+
+    @classmethod
+    def do_save(cls, user_id, request_dict, response, request):
+        """
+        添加网关设备
+        @param request:
+        @param user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        nick_name = request_dict.get('nickName', None)
+        serial_number = request_dict.get('serialNumber', None)
+        device_type = request_dict.get('deviceType', None)
+        family_id = request_dict.get('familyId', None)
+        room_id = request_dict.get('roomId', None)
+        # type 可能为0
+        if not all([nick_name, serial_number, device_type]):
+            return response.json(444, {'param': 'nick_name, serial_number, device_type'})
+        device_info_qs = Device_Info.objects.filter(serial_number=serial_number, isShare=False)
+        if device_info_qs:
+            qs = device_info_qs.values('userID__NickName', 'NickName', 'userID_id')
+            nickname = qs[0]['NickName']
+            device_user_id = qs[0]['userID_id']
+            if device_user_id == user_id:
+                return response.json(174)
+            else:
+                if qs[0]['userID__userEmail']:
+                    bind_user = qs[0]['userID__userEmail']
+                elif qs[0]['userID__phone']:
+                    bind_user = qs[0]['userID__phone']
+                elif qs[0]['userID__username']:
+                    bind_user = qs[0]['userID__username']
+                elif qs[0]['userID__NickName']:
+                    bind_user = qs[0]['userID__NickName']
+                else:
+                    bind_user = qs[0]['userID_id']
+                res = {
+                    'bindUser': bind_user,
+                    'bindDeviceName': nickname,
+                    'isMainUserExists': 1
+                }
+                return response.json(0, res=res)
+        try:
+            with transaction.atomic():
+                family_id = int(family_id)
+                # 格式化后的日期时间
+                now_time = CommonService.timestamp_to_str(int(time.time()))
+                device_id = CommonService.getUserID(getUser=False)
+                Device_Info.objects.create(id=device_id, userID_id=user_id, NickName=nick_name,
+                                           Type=device_type,
+                                           UID=serial_number,
+                                           serial_number=serial_number, data_joined=now_time,
+                                           update_time=now_time)
+                boole = cls.family_room_device_save(family_id, room_id, device_id)
+                if not boole:
+                    return response.json(15)
+                # 判断是否有用户绑定
+                us_qs = UidSetModel.objects.filter(uid=serial_number)
+                if not us_qs:
+                    n_time = int(time.time())
+                    ip = CommonService.get_ip_address(request)
+                    region_id = Device_Region().get_device_region(ip)
+                    region_alexa = 'CN' if region_id == 1 else 'ALL'
+                    uid_set_create_dict = {
+                        'uid': serial_number,
+                        'addTime': n_time,
+                        'updTime': n_time,
+                        'ip': CommonService.get_ip_address(request_dict),
+                        'nickname': nick_name,
+                        'region_alexa': region_alexa,
+                    }
+                    UidSetModel.objects.create(**uid_set_create_dict)
+                return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def get_device_not_in_room(cls, user_id, request_dict, response):
+        """
+        获取不在房间的设备
+        @param user_id: 用户id
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        page_no = request_dict.get('pageNo', None)
+        page_size = request_dict.get('pageSize', None)
+        family_id = request_dict.get('familyId', None)
+        if not all([page_no, page_size, family_id]):
+            return response.json(444)
+        page_no = int(page_no)
+        page_size = int(page_size)
+        device_list = cls.get_family_device_list(user_id, page_no, page_size, family_id, 0)
+        return response.json(0, device_list)
+
+    @classmethod
+    def do_device_query(cls, user_id, request_dict, response):
+        """
+        查询用户设备信息
+        @param user_id: 用户id
+        @param request_dict: 请求参数
+        @param response: 响应对象
+        @return: response
+        """
+        page = request_dict.get('page', None)
+        line = request_dict.get('line', None)
+        nick_name = request_dict.get('NickName', None)
+        family_id = request_dict.get('familyId', None)
+        room_id = request_dict.get('roomId', None)
+
+        page = int(page)
+        line = int(line)
+        uid = request_dict.get('uid', None)
+        if family_id:
+            permission = cls.get_member_permission_details(user_id, int(family_id))
+            if not permission or permission == '003':
+                return response.json(404)
+        if room_id:
+            family_room = FamilyRoom.objects.filter(id=room_id)
+            if not family_room.exists():
+                return response.json(173)
+            permission = cls.get_member_permission_details(user_id, family_room.first().family_id)
+            if not permission or permission == '003':
+                return response.json(404)
+        # 获取设备信息列表
+        device_info_list = cls.get_device_info_list(user_id, nick_name, uid,
+                                                    page, line, family_id, room_id)
+        uid_list = []
+        # 判断是否是主用户 isPrimaryUser=0:否,1:是
+        for dvl in device_info_list:
+            if dvl['primaryUserID'] and dvl['id'] == dvl['primaryUserID']:
+                dvl['isPrimaryUser'] = 1
+            else:
+                dvl['isPrimaryUser'] = 0
+            uid_list.append(dvl['UID'])
+        # 设备关联套餐,设备预览图
+        uid_bucket_qs, uid_preview_qs = cls.get_bucket_and_preview_by_uid(uid_list)
+        # 设备配置信息
+        uid_set_dict = cls.get_uid_set_dict(uid_list)
+        # 设备详情信息
+        result = cls.get_device_details(device_info_list, uid_bucket_qs, uid_preview_qs, uid_set_dict)
+        items = []
+        for index, item in enumerate(result):
+            # 加密
+            if item['View_Password']:
+                item['View_Password'] = CommonService.encode_data(item['View_Password'], 1, 4)
+            items.append(item)
+        return response.json(0, items)
+
+    @classmethod
+    def get_device_info_list(cls, user_id, nick_name, uid, page, line, family_id, room_id):
+        """
+        根据用户id获取设备信息
+        @param room_id: 家庭id
+        @param family_id: 房间id
+        @param uid: uid
+        @param nick_name: 设备名称
+        @param line: 条数
+        @param page: 页数
+        @param user_id: 用户id
+        @return: device_info_list 设备信息列表
+        """
+        # 获取用户设备信息
+        device_info_qs = Device_Info.objects.filter(userID_id=user_id)
+        # 过滤已重置的设备
+        device_info_qs = device_info_qs.filter(~Q(isExist=2))
+        if nick_name:
+            device_info_qs = device_info_qs.filter(NickName__icontains=nick_name)
+        if uid:
+            device_info_qs.filter(UID=uid)
+        if family_id or room_id:
+            # 根据家庭id获取房间id关联查询设备
+            return cls.get_family_device_list(user_id, page, line, family_id, room_id)
+        device_info_values = device_info_qs.values('id', 'userID', 'NickName', 'UID', 'View_Account', 'View_Password',
+                                                   'ChannelIndex',
+                                                   'Type', 'isShare', 'primaryUserID', 'primaryMaster', 'data_joined',
+                                                   'vodPrimaryUserID',
+                                                   'vodPrimaryMaster', 'userID__userEmail', 'version', 'isVod',
+                                                   'isExist', 'NotificationMode',
+                                                   'isCameraOpenCloud', 'serial_number')
+
+        device_info_values = device_info_values[(page - 1) * line:page * line]
+        device_info_list = CommonService.qs_to_list(device_info_values)
+        return device_info_list
+
+    @classmethod
+    def get_family_device_list(cls, user_id, page_no, page_size, family_id, room_id, is_room_other=False):
+        """
+        获取关联家庭设备列表
+        @param is_room_other: 是否显示其他房间设备
+        @param user_id: 用户id
+        @param page_no: 页数
+        @param page_size: 分页大小
+        @param family_id: 家庭id
+        @param room_id: 房间id
+        @return: result_list
+        """
+        cursor = connection.cursor()
+        sql = 'SELECT d.id,d.userID_id as userID,d.NickName,d.UID,d.View_Account,d.View_Password,d.ChannelIndex,' \
+              'd.Type,d.isShare,d.primaryUserID,d.primaryMaster,d.data_joined,d.vodPrimaryUserID,d.vodPrimaryMaster, ' \
+              'd.version,d.isVod,d.isExist,d.NotificationMode,d.isCameraOpenCloud,d.serial_number '
+        sql += 'FROM device_info d INNER JOIN family_room_device l ON d.id = l.device_id '
+        sql += 'WHERE d.userID_id = %s AND d.isExist != %s '
+        if family_id:
+            family_id = int(family_id)
+            user_id = UserFamily.objects.filter(id=family_id).first().user_id
+            sql += ' AND l.family_id = %s '
+        if room_id and is_room_other:
+            sql += ' AND l.room_id != %s '
+        elif room_id:
+            sql += ' AND l.room_id = %s '
+        sql += ' GROUP BY d.id order by d.data_joined DESC,d.id DESC LIMIT %s,%s '
+        if family_id and room_id:
+            cursor.execute(sql, [user_id, 2, int(family_id), int(room_id), ((page_no - 1) * page_size),
+                                 page_size, ])
+            data_obj = cursor.fetchall()
+        else:
+            cursor.execute(sql, [user_id, 2, int(family_id) if family_id else int(room_id), ((page_no - 1) * page_size),
+                                 page_size, ])
+            data_obj = cursor.fetchall()
+        cursor.close()  # 执行完,关闭
+        connection.close()
+        result_list = []
+        col_names = [desc[0] for desc in cursor.description]
+        for item in data_obj:
+            val = dict(zip(col_names, item))
+            user_id = val['userID']
+            device_user_qs = Device_User.objects.filter(userID=user_id)
+            val['userID__userEmail'] = device_user_qs.first().userEmail
+            val['isShare'] = False if val['isShare'] == 0 else True
+            if 'data_joined' in val:
+                if val['data_joined']:
+                    val['data_joined'] = val['data_joined'].strftime("%Y-%m-%d %H:%M:%S")
+                else:
+                    val['data_joined'] = ''
+            result_list.append(val)
+        return result_list
+
+    @classmethod
+    def get_bucket_and_preview_by_uid(cls, uid_list):
+        """
+        根据uid列表查询套餐
+        @param uid_list: uid列表
+        @return: uid_bucket_qs
+        """
+        uid_bucket_qs = UID_Bucket.objects.filter(uid__in=uid_list).values('bucket__content', 'status', 'channel',
+                                                                           'endTime', 'uid')
+        uid_preview_qs = UID_Preview.objects.filter(uid__in=uid_list).order_by('channel').values('id', 'uid', 'channel')
+        return uid_bucket_qs, uid_preview_qs
+
+    @classmethod
+    def get_uid_set_dict(cls, uid_list):
+        """
+        获取uid配置信息
+        @param uid_list: uid列表
+        @return: uid_set_dict uid配置信息
+        """
+        uid_set_qs = UidSetModel.objects.filter(uid__in=uid_list) \
+            .values('id', 'uid', 'version', 'nickname', 'ucode',
+                    'detect_status', 'detect_group',
+                    'detect_interval',
+                    'region_alexa', 'is_alexa', 'deviceModel',
+                    'TimeZone', 'TimeStatus', 'SpaceUsable',
+                    'SpaceSum', 'MirrorType', 'RecordType',
+                    'OutdoorModel', 'WIFIName', 'isDetector',
+                    'DetectorRank', 'is_human', 'is_custom_voice',
+                    'is_ptz', 'double_wifi', 'is_ai')
+        uid_set_dict = {}
+        for us in uid_set_qs:
+            uid_set_dict[us['uid']] = {
+                'version': us['version'],
+                'nickname': us['nickname'],
+                'ucode': us['ucode'],
+                'detect_interval': us['detect_interval'],
+                'detect_group': us['detect_group'],
+                'detect_status': us['detect_status'],
+                'region_alexa': us['region_alexa'],
+                'is_alexa': us['is_alexa'],
+                'deviceModel': us['deviceModel'],
+                'TimeZone': us['TimeZone'],
+                'TimeStatus': us['TimeStatus'],
+                'SpaceUsable': us['SpaceUsable'],
+                'SpaceSum': us['SpaceSum'],
+                'MirrorType': us['MirrorType'],
+                'RecordType': us['RecordType'],
+                'OutdoorModel': us['OutdoorModel'],
+                'WIFIName': us['WIFIName'],
+                'isDetector': us['isDetector'],
+                'DetectorRank': us['DetectorRank'],
+                'is_human': us['is_human'],
+                'is_custom_voice': us['is_custom_voice'],
+                'is_ptz': us['is_ptz'],
+                'double_wifi': us['double_wifi'],
+                'is_ai': us['is_ai']
+            }
+            # 从uid_channel里面取出通道配置信息
+            uid_channel_set_qs = UidChannelSetModel.objects.filter(uid__id=us['id']) \
+                .values('channel', 'channel_name',
+                        'pir_audio', 'mic_audio',
+                        'battery_status',
+                        'battery_level',
+                        'sleep_status',
+                        'sleep_time',
+                        'light_night_model',
+                        'light_alarm_type',
+                        'light_alarm_level',
+                        'light_alarm_man_en',
+                        'light_alarm_vol',
+                        'light_long_light'
+                        )
+            channels_list = []
+            for ucs in uid_channel_set_qs:
+                channels_dict = {
+                    'channel': ucs['channel'],
+                    'channel_name': ucs['channel_name'],
+                    'pir_audio': ucs['pir_audio'],
+                    'mic_audio': ucs['mic_audio'],
+                    'battery_status': ucs['battery_status'],
+                    'battery_level': ucs['battery_level'],
+                    'sleep_status': ucs['sleep_status'],
+                    'sleep_time': ucs['sleep_time'],
+                    'light_night_model': ucs['light_night_model'],
+                    'light_alarm_type': ucs['light_alarm_type'],
+                    'light_alarm_level': ucs['light_alarm_level'],
+                    'light_alarm_man_en': ucs['light_alarm_man_en'],
+                    'light_alarm_vol': ucs['light_alarm_vol'],
+                    'light_long_light': ucs['light_long_light']
+                }
+                channels_list.append(channels_dict)
+            uid_set_dict[us['uid']]['channels'] = channels_list
+        return uid_set_dict
+
+    @classmethod
+    def get_device_details(cls, device_info_list, uid_bucket_qs, uid_preview_qs, uid_set_dict):
+        """
+        设备详情
+        @param device_info_list: 设备信息列表
+        @param uid_bucket_qs: 套餐对象
+        @param uid_preview_qs:
+        @param uid_set_dict:
+        @return:
+        """
+        now_time = int(time.time())
+        data = []
+        for p in device_info_list:
+            # 获取iotDeviceInfo表的endpoint和tokenIotNumber
+            p['iot'] = []
+            if p['serial_number']:  # 存在序列号根据序列号查询
+                iot_device_info_qs = iotdeviceInfoModel.objects.filter(serial_number=p['serial_number'][0:6])
+            else:  # 根据uid查询
+                iot_device_info_qs = iotdeviceInfoModel.objects.filter(uid=p['UID'])
+            if iot_device_info_qs.exists():
+                iot_device_Info = iot_device_info_qs.values('endpoint', 'token_iot_number')
+                p['iot'].append({
+                    'endpoint': iot_device_Info[0]['endpoint'],
+                    'token_iot_number': iot_device_Info[0]['token_iot_number']
+                })
+
+            p['vod'] = []
+            for dm in uid_bucket_qs:
+                if p['UID'] == dm['uid']:
+                    if dm['endTime'] > now_time:
+                        p['vod'].append(dm)
+            p['preview'] = []
+
+            auth = oss2.Auth(OSS_STS_ACCESS_KEY, OSS_STS_ACCESS_SECRET)
+            bucket = oss2.Bucket(auth, 'oss-cn-hongkong.aliyuncs.com', 'statres')
+
+            for up in uid_preview_qs:
+                if p['UID'] == up['uid']:
+                    obj = 'uid_preview/{uid}/channel_{channel}.png'.format(uid=up['uid'], channel=up['channel'])
+                    img_sign = bucket.sign_url('GET', obj, 300)
+                    p['preview'].append(img_sign)
+
+            p_uid = p['UID']
+
+            # 返回设备初始化字符
+            uid_qs = UIDModel.objects.filter(uid=p_uid).values('platform', 'init_string', 'init_string_app')
+            if uid_qs.exists():
+                p['platform'] = uid_qs[0]['platform']
+                p['initString'] = uid_qs[0]['init_string']
+                p['initStringApp'] = uid_qs[0]['init_string_app']
+
+            if p_uid in uid_set_dict:
+                # 设备版本号
+                uidversion = uid_set_dict[p_uid]['version']
+                if len(uidversion) > 6:
+                    uidversion = uidversion[0: uidversion.rfind('.')]
+                p['uid_version'] = uidversion
+                p['ucode'] = uid_set_dict[p_uid]['ucode']
+                p['detect_interval'] = uid_set_dict[p_uid]['detect_interval']
+                p['detect_status'] = uid_set_dict[p_uid]['detect_status']
+                p['detect_group'] = uid_set_dict[p_uid]['detect_group']
+                p['region_alexa'] = uid_set_dict[p_uid]['region_alexa']
+                p['is_alexa'] = uid_set_dict[p_uid]['is_alexa']
+                p['deviceModel'] = uid_set_dict[p_uid]['deviceModel']
+                p['TimeZone'] = uid_set_dict[p_uid]['TimeZone']
+                p['TimeStatus'] = uid_set_dict[p_uid]['TimeStatus']
+                p['SpaceUsable'] = uid_set_dict[p_uid]['SpaceUsable']
+                p['SpaceSum'] = uid_set_dict[p_uid]['SpaceSum']
+                p['MirrorType'] = uid_set_dict[p_uid]['MirrorType']
+                p['RecordType'] = uid_set_dict[p_uid]['RecordType']
+                p['OutdoorModel'] = uid_set_dict[p_uid]['OutdoorModel']
+                p['WIFIName'] = uid_set_dict[p_uid]['WIFIName']
+                p['isDetector'] = uid_set_dict[p_uid]['isDetector']
+                p['DetectorRank'] = uid_set_dict[p_uid]['DetectorRank']
+                p['is_human'] = uid_set_dict[p_uid]['is_human']
+                p['is_custom_voice'] = uid_set_dict[p_uid]['is_custom_voice']
+                p['is_ptz'] = uid_set_dict[p_uid]['is_ptz']
+                p['channels'] = uid_set_dict[p_uid]['channels']
+                p['double_wifi'] = uid_set_dict[p_uid]['double_wifi']
+                p['is_ai'] = uid_set_dict[p_uid]['is_ai']
+                # 设备昵称 调用影子信息昵称,先阶段不可
+                if uid_set_dict[p_uid]['nickname']:
+                    p['NickName'] = uid_set_dict[p_uid]['nickname']
+            else:
+                # 设备版本号
+                p['uid_version'] = ''
+                p['ucode'] = ''
+            data.append(p)
+        return data
+
+    @classmethod
+    def get_family_list(cls, user_id, request_dict, response):
+        """
+        查询我的家庭列表
+        @param user_id: 用户id
+        @param request_dict: 请求
+        @param response: 响应
+        @return: 家庭列表items
+        """
+        lang = request_dict.get('lang', 'cn')
+        if user_id:
+            with transaction.atomic():
+                user_family_qs = UserFamily.objects.filter(user_id=user_id)
+                if not user_family_qs.exists():
+                    n_time = int(time.time())
+                    device_user = Device_User.objects.get(userID=user_id)
+                    # 创建默认家庭使用用户名或者邮箱作为名称
+                    family_name = device_user.username if device_user.username else device_user.userEmail
+                    family_name = family_name + "的家" if lang == 'cn' else family_name + " home"
+                    user_family = UserFamily.objects.create(user_id=user_id, name=family_name,
+                                                            updated_time=n_time,
+                                                            created_time=n_time)
+                    if user_family.id:
+                        member_permission_qs = FamilyMemberPermission.objects.filter(no='001').values('id')
+                        permission_id = member_permission_qs.first()['id']
+                        FamilyMember.objects.create(family_id=user_family.id, user_id=user_id,
+                                                    user_name=device_user.username, identity=1,
+                                                    permission_id=int(permission_id), sort=1, updated_time=n_time,
+                                                    created_time=n_time)
+                        cls.family_device_binding(user_id, family_id=user_family.id)
+
+                family_member_qs = FamilyMember.objects.filter(user_id=user_id) \
+                    .order_by('sort').values('identity', 'family_id', 'family__name', 'permission_id', 'permission__no',
+                                             'family__location', 'user__username', 'user__userIconUrl')
+                items = EquipmentFamilyView.family_info_list(family_member_qs)
+                return response.json(0, items)
+        return response.json(309)
+
+    @staticmethod
+    def who_family_list(user_id, response):
+        """
+        查询我加入的家庭集合与我创建的家庭集合
+        @param user_id:
+        @param response:
+        @return:
+        """
+        join_family_qs = FamilyMember.objects.filter(user_id=user_id, identity=0) \
+            .order_by('sort').values('identity', 'family_id', 'family__name', 'permission_id', 'permission__no',
+                                     'family__location', 'user__username', 'user__userIconUrl')
+        join_family_list = EquipmentFamilyView.family_info_list(join_family_qs)
+
+        my_family_qs = FamilyMember.objects.filter(user_id=user_id, identity=1) \
+            .order_by('sort').values('identity', 'family_id', 'family__name', 'permission_id', 'permission__no',
+                                     'family__location', 'user__username', 'user__userIconUrl')
+        my_family_list = EquipmentFamilyView.family_info_list(my_family_qs)
+
+        return response.json(0, {'myFamilyList': my_family_list, 'joinFamilyList': join_family_list})
+
+    @staticmethod
+    def family_info_list(family_member_qs):
+        items = []
+        data = {}
+        for item in family_member_qs:
+            data['familyId'] = item['family_id']
+            data['identity'] = item['identity']
+            data['familyName'] = item['family__name']
+            data['permissionId'] = item['permission_id']
+            data['permissionNo'] = item['permission__no']
+            data['familyLocation'] = item['family__location']
+            data['userName'] = item['user__username']
+            data['userIconUrl'] = item['user__userIconUrl']
+            room_qs = FamilyRoom.objects.filter(family_id=data['familyId']).order_by('sort') \
+                .values('id', 'name')
+            data['rooms'] = list(room_qs)
+            items.append(data)
+            data = {}
+        return items
+
+    @classmethod
+    def get_family_setting(cls, request_dict, response):
+        """
+        家庭设置
+        @param request_dict: 请求
+        @param response: 响应
+        @return: 家庭列表items
+        """
+        family_id = request_dict.get('familyId', None)
+        if not family_id:
+            return response.json(444)
+        family_id = int(family_id)
+        user_family_qs = UserFamily.objects.filter(id=family_id).values('id', 'name', 'location')
+        if not user_family_qs.exists():
+            return response.json(404)
+        family_dict = user_family_qs.first()
+        device_count = FamilyRoomDevice.objects.filter(family_id=family_id, sub_device=0).count()
+        family_dict['deviceCount'] = device_count
+        family_dict['roomCount'] = FamilyRoom.objects.filter(family_id=family_id).count()
+
+        family_member_qs = FamilyMember.objects.filter(family_id=family_id)
+        family_member_qs = family_member_qs.values('identity', 'family_id',
+                                                   'permission_id',
+                                                   'permission__no',
+                                                   'user__username',
+                                                   'user_id',
+                                                   'user__userIconUrl',
+                                                   'user__NickName',
+                                                   'user__phone',
+                                                   'user__userEmail',
+                                                   'id')
+        family_member_qs = family_member_qs.order_by('-identity').order_by('sort')
+        items = []
+        data = {}
+        for item in family_member_qs:
+            data['userName'] = item['user__username']
+            data['userIconUrl'] = item['user__userIconUrl']
+            data['userId'] = item['user_id']
+            data['identity'] = item['identity']
+            data['permissionId'] = item['permission_id']
+            data['permissionNo'] = item['permission__no']
+            data['nickName'] = item['user__NickName']
+            data['phone'] = item['user__phone']
+            data['userEmail'] = item['user__userEmail']
+            data['memberId'] = item['id']
+            items.append(data)
+            data = {}
+        family_dict['members'] = items
+        return response.json(0, family_dict)
+
+    @classmethod
+    def family_save(cls, user_id, request_dict, response):
+        """
+        家庭保存
+        @param user_id: 用户id
+        @param request_dict: 参数
+        @param response: 响应
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        family_name = request_dict.get('familyName', None)
+        location = request_dict.get('location', None)
+        with transaction.atomic():
+            now_time = int(time.time())
+            if family_id:
+                is_owner = cls.get_family_owner(user_id, family_id)
+                if not is_owner:
+                    return response.json(404)
+                family_member = FamilyMember.objects.filter(family_id=family_id, user_id=user_id)
+                if family_member.exists():
+                    family_member = family_member.first()
+                    if family_member.identity == 0:
+                        return response.json(404)
+                    family_qs = UserFamily.objects.filter(id=family_id)
+                    if family_qs.exists():
+                        data = {
+                            'updated_time': now_time
+                        }
+                        if family_name:
+                            data['name'] = family_name
+                        if location:
+                            data['location'] = location
+                        family_qs.update(**data)
+                        return response.json(0)
+            data = {'user_id': user_id, 'updated_time': now_time, 'created_time': now_time}
+            if family_name:
+                data['name'] = family_name
+                if location:
+                    data['location'] = location
+                UserFamily.objects.create(**data)
+                return response.json(0)
+            return response.json(444)
+
+    @classmethod
+    def family_room_device_save(cls, family_id, room_id, device_id):
+        """
+        设备与家庭房间保存
+        @param family_id: 家庭id
+        @param room_id: 房间id
+        @param device_id: 设备id
+        @return: Boole
+        """
+        now_time = int(time.time())
+        family_room_device = FamilyRoomDevice.objects.filter(device_id=device_id)
+        if family_room_device.exists():
+            return False
+        data = {
+            'family_id': int(family_id),
+            'device_id': device_id,
+            'updated_time': now_time,
+            'created_time': now_time
+        }
+        if room_id:
+            room_id = int(room_id)
+            if FamilyRoom.objects.filter(id=room_id).exists():
+                data['room_id'] = room_id
+        FamilyRoomDevice.objects.create(**data)
+        return True
+
+    @classmethod
+    def family_device_binding(cls, user_id, family_id):
+        """
+        用户旧设备与默认家庭进行绑定
+        @param user_id:
+        @param family_id:
+        @return: True
+        """
+        device_info_qs = Device_Info.objects.filter(userID=user_id)
+        device_info_qs = device_info_qs.filter(~Q(isExist=0)).values('id')
+        if device_info_qs.exists():
+            with transaction.atomic():
+                not_time = time.time()
+                device_list = []
+                for item in device_info_qs:
+                    device_id = item['id']
+                    family_device_qs = FamilyRoomDevice.objects.filter(device_id=device_id)
+                    if not family_device_qs.exists():
+                        # 设备绑定家庭
+                        device_list.append(FamilyRoomDevice(family_id=family_id, device_id=device_id,
+                                                            created_time=not_time,
+                                                            updated_time=not_time))
+                if device_list:
+                    FamilyRoomDevice.objects.bulk_create(device_list)
+        return True
+
+    @classmethod
+    def get_family_room_list(cls, request_dict, response):
+        """
+        获取房间列表并统计该房间下有几台设备
+        @param request_dict: 请求参数
+        @param response: 响应参数
+        @return: total;data
+        """
+        family_id = request_dict.get('familyId', None)
+        if not family_id:
+            return response.json(444)
+        count = FamilyRoomDevice.objects.filter(family_id=family_id).values('device_id').annotate(
+            count=Count('device_id')).count()
+
+        room_qs = FamilyRoom.objects.filter(family_id=family_id).order_by('sort')
+        total = room_qs.count()
+        room_qs = room_qs.values('id', 'name', 'sort')
+        room_list = []
+        if not room_qs.exists():
+            return response.json(0, {'total': 0, 'data': room_list, 'deviceTotal': count})
+        for item in room_qs:
+            item['deviceCount'] = FamilyRoomDevice.objects.filter(family_id=family_id, room_id=item['id']).count()
+            room_list.append(item)
+        return response.json(0, {'total': total, 'data': room_list, 'deviceTotal': count})
+
+    @classmethod
+    def room_save(cls, request_dict, response):
+        """
+        房间保存
+        @param request_dict: 请求参数
+        @param response: 响应参数
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        room_name = request_dict.get('roomName', None)
+        room_id = request_dict.get('roomId', None)
+        if not all([family_id, room_name]):
+            return response.json(444)
+        with transaction.atomic():
+            now_time = int(time.time())
+            if room_id:
+                room_qs = FamilyRoom.objects.filter(id=int(room_id))
+                if room_qs.exists():
+                    room_qs.update(name=room_name, updated_time=now_time)
+                    return response.json(0)
+            room_qs = FamilyRoom.objects.filter(family_id=family_id, name=room_name)
+
+            if room_qs.exists():
+                return response.json(174)
+            FamilyRoom.objects.create(family_id=family_id, name=room_name, updated_time=now_time,
+                                      created_time=now_time)
+        return response.json(0)
+
+    @classmethod
+    def changes_member_permission(cls, app_user_id, request_dict, response):
+        """
+        更新家庭成员权限
+        @param app_user_id: 当前app登录用户
+        @param request_dict: 请求参数
+        @param response: 响应实体
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        user_id = request_dict.get('userId', None)
+        no = request_dict.get('no', None)
+        if not all([family_id, user_id, no]):
+            return response.json(444)
+        owner = cls.get_family_owner(app_user_id, family_id)
+        if not owner:
+            return response.json(404)
+        permission_qs = FamilyMemberPermission.objects.filter(no=no).values()
+        if permission_qs.exists():
+            permission_qs = permission_qs.first()
+            p_id = permission_qs['id']
+            FamilyMember.objects.filter(family_id=family_id, user_id=user_id).update(permission_id=p_id)
+        return response.json(0)
+
+    @classmethod
+    def family_member_del(cls, app_user_id, request_dict, response):
+        """
+        家庭成员删除
+        @param app_user_id: 当前app登录用户
+        @param request_dict: 请求参数
+        @param response: 响应实体
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        user_id = request_dict.get('userId', None)
+        if not all([family_id, user_id]):
+            return response.json(444)
+        owner = cls.get_family_owner(app_user_id, family_id)
+        if not owner:
+            return response.json(404)
+        family_member_qs = FamilyMember.objects.filter(family_id=family_id, user_id=user_id)
+        if family_member_qs.exists():
+            family_member_qs.delete()
+        return response.json(0)
+
+    @classmethod
+    def get_member_permission_list(cls, app_user_id, request_dict, response):
+        """
+        获取用户权限列表
+        @param app_user_id: 当前app登录用户
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        user_id = request_dict.get('userId', app_user_id)
+        if not family_id:
+            return response.json(404)
+        result = cls.get_member_permission_by_family_id(user_id, family_id)
+        return response.json(0, result)
+
+    @classmethod
+    def get_member_permission_by_family_id(cls, user_id, family_id):
+        """
+        获取权限列表并返回当前user_id所在家庭中权限
+        @param user_id:
+        @param family_id:
+        @return:
+        """
+        member_qs = FamilyMember.objects.filter(family_id=family_id)
+        if user_id:
+            member_qs = member_qs.filter(user_id=user_id).values()
+            if member_qs.exists():
+                member_qs = member_qs.first()
+        permission = FamilyMemberPermission.objects.all().values('id', 'no')
+        data_list = []
+        this_permission = {}
+        result = {}
+        for item in permission:
+            if item['id'] == member_qs['permission_id']:
+                this_permission['id'] = item['id']
+                this_permission['no'] = item['no']
+            data_list.append(item)
+        result['memberPermission'] = this_permission
+        result['permissionList'] = data_list
+        return result
+
+    @classmethod
+    def get_member_permission_details(cls, user_id, family_id):
+        """
+        根据用户id获取家庭设备权限
+        @param user_id:
+        @param family_id:
+        @return: 权限编号 001:所有权限,002:查看设备,003:暂无权限
+        """
+        member_qs = FamilyMember.objects.filter(family_id=family_id, user_id=user_id).values()
+        if member_qs.exists():
+            member_qs = member_qs.first()
+            permission_id = member_qs['permission_id']
+            permission_qs = FamilyMemberPermission.objects.filter(id=permission_id).values('no')
+            return permission_qs.first()['no']
+        return ''
+
+    @classmethod
+    def get_family_owner(cls, user_id, family_id):
+        """
+        判断是否是家庭主用户
+        @param user_id:
+        @param family_id:
+        @return:
+        """
+        user_family_qs = UserFamily.objects.filter(id=family_id, user_id=user_id)
+        if user_family_qs.exists():
+            return True
+        return False

+ 263 - 0
Controller/SensorGateway/GatewayDeviceController.py

@@ -0,0 +1,263 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : GatewayDeviceController.py
+@Time    : 2022/6/6 13:50
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+from django.db import transaction
+from django.db.models import Q
+from django.views.generic.base import View
+
+from Controller.SensorGateway.EquipmentFamilyController import EquipmentFamilyView
+from Model.models import FamilyRoomDevice, FamilyRoom, GatewaySubDevice, Device_Info, UserFamily, FamilyMember, \
+    UidSetModel, iotdeviceInfoModel
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+
+
+# 网关主页
+class GatewayDeviceView(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 validation(self, request_dict, request, operation):
+
+        token = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
+        lang = request_dict.get('lang', None)
+        response = ResponseObject(lang) if lang else ResponseObject(token.lang)
+        if token.code != 0:
+            return response.json(token.code)
+        user_id = token.userID
+
+        # 网关设备
+        if operation == 'list':
+            return self.gateway_device_list(request_dict, response)
+        elif operation == 'del':
+            return self.gateway_device_del(user_id, request_dict, response)
+        elif operation == 'update':
+            return self.gateway_device_update(user_id, request_dict, response)
+        elif operation == 'my/family/list':
+            return self.my_family_list(user_id, response)
+        elif operation == 'location-setting':
+            return self.device_location_setting(user_id, request_dict, response)
+
+    @classmethod
+    def device_location_setting(cls, user_id, request_dict, response):
+        """
+        网关位置迁移
+        @param user_id: 用户id
+        @param request_dict: 请求参数字典
+        @param response: 响应对象
+        @return: []
+        """
+        device_id = request_dict.get('deviceId', None)
+        family_id = request_dict.get('familyId', None)
+        room_id = request_dict.get('roomId', None)
+        if not all([device_id, family_id]):
+            return response.json(444)
+        family_id = int(family_id)
+        permission = EquipmentFamilyView.get_member_permission_details(user_id, family_id)
+        if not permission or permission == '003':
+            return response.json(404)
+        try:
+            with transaction.atomic():
+                family_room_device_qs = FamilyRoomDevice.objects.filter(device_id=device_id, family_id=family_id)
+                if family_room_device_qs.exists():
+                    family_room_device_qs = family_room_device_qs.filter(sub_device=0)
+                    if family_room_device_qs.exists() and room_id:
+                        family_room_device_qs.update(room_id=int(room_id))
+                else:
+                    user_family_qs = UserFamily.objects.filter(id=family_id)
+                    if not user_family_qs:
+                        return response.json(173)
+                    family_room_device_qs = FamilyRoomDevice.objects.filter(device_id=device_id)
+                    if family_room_device_qs.exists():
+                        param_data = {'family_id': family_id, 'room_id': 0}
+                        if room_id:
+                            param_data['room_id'] = room_id
+                        family_room_device_qs.update(**param_data)
+                return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def gateway_device_update(cls, user_id, request_dict, response):
+        """
+        网关设备修改名称
+        @param user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        device_name = request_dict.get('deviceName')
+        device_id = request_dict.get('deviceId')
+        if not all([device_name, device_id]):
+            return response.json(444)
+        device_info_qs = Device_Info.objects.filter(userID_id=user_id, id=device_id)
+        if device_info_qs.exists():
+            device_info_qs.update(NickName=device_name)
+        return response.json(0)
+
+    @classmethod
+    def my_family_list(cls, user_id, response):
+        """
+        我的家庭列表
+        @param user_id:
+        @param response:
+        @return:
+        """
+        user_family_qs = UserFamily.objects.filter(user_id=user_id).values()
+        family_list = []
+        if user_family_qs.exists():
+            family_member_qs = FamilyMember.objects.filter(user_id=user_id, identity=1) \
+                .order_by('sort').values('identity', 'family_id', 'family__name', 'permission_id', 'permission__no',
+                                         'family__location', 'user__username', 'user__userIconUrl')
+            items = EquipmentFamilyView.family_info_list(family_member_qs)
+            return response.json(0, items)
+        return response.json(0, family_list)
+
+    @classmethod
+    def gateway_device_del(cls, user_id, request_dict, response):
+        """
+        网关设备删除或删除子设备
+        @param user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        device_id = request_dict.get('deviceId')
+        family_id = request_dict.get('familyId')
+        # 1 删除网关 否则删除子设备
+        sub_ids = request_dict.get('subIds')
+        if not family_id:
+            return response.json(444)
+        permission = EquipmentFamilyView.get_member_permission_details(user_id, family_id)
+        if not permission or permission == '003':
+            return response.json(404)
+        try:
+            with transaction.atomic():
+                if device_id:
+                    device_qs = Device_Info.objects.filter(id=device_id)
+                    if device_qs.exists():
+                        family_device_qs = FamilyRoomDevice.objects.filter(device_id=device_id)
+                        if family_device_qs.exists():
+                            family_device_qs.delete()
+                        gateway_qs = GatewaySubDevice.objects.filter(device_id=device_id)
+                        if gateway_qs.exists():
+                            gateway_qs.delete()
+                        uid_set_qs = UidSetModel.objects.filter(uid=device_qs.first().UID)
+                        if uid_set_qs.exists():
+                            uid_set_qs.delete()
+                        device_qs.delete()
+                elif sub_ids:
+                    sub_ids = sub_ids.split(',')
+                    ids = []
+                    for item in sub_ids:
+                        sub_id = int(item)
+                        ids.append(sub_id)
+                    family_device_qs = FamilyRoomDevice.objects.filter(sub_device__in=ids)
+                    if family_device_qs.exists():
+                        family_device_qs.delete()
+                    gateway_sub_qs = GatewaySubDevice.objects.filter(id__in=ids)
+                    if gateway_sub_qs.exists():
+                        gateway_sub_qs.delete()
+                return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def gateway_device_list(cls, request_dict, response):
+        """
+        网关设备列表
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        device_id = request_dict.get('deviceId', None)
+        if not device_id:
+            return response.json(444)
+        device_qs = FamilyRoomDevice.objects.filter(device_id=device_id, sub_device=0)
+        if not device_qs.exists():
+            return response.json(173)
+        try:
+            device_qs = device_qs.values('family_id', 'device_id', 'room_id', 'device__Type', 'device__NickName',
+                                         'device__UID',
+                                         'device__serial_number')
+            device_qs = device_qs.first()
+            room_id = device_qs['room_id']
+            family_id = device_qs['family_id']
+            gateway_room_name = ''
+            if room_id:
+                room_qs = FamilyRoom.objects.filter(id=room_id)
+                gateway_room_name = room_qs.first().name if room_qs.exists() else ''
+
+            iot_device_info_qs = iotdeviceInfoModel.objects.filter(
+                serial_number=device_qs['device__serial_number'][0:6])
+            iot_data = {}
+            if iot_device_info_qs.exists():
+                iot_device_Info = iot_device_info_qs.values('endpoint', 'token_iot_number')
+                iot_data = {
+                    'endpoint': iot_device_Info[0]['endpoint'],
+                    'token_iot_number': iot_device_Info[0]['token_iot_number']
+                }
+            gateway = {
+                'deviceId': device_qs['device_id'],
+                'deviceType': device_qs['device__Type'],
+                'deviceNickName': device_qs['device__NickName'],
+                'UID': device_qs['device__UID'],
+                'serialNumber': device_qs['device__serial_number'],
+                'roomName': gateway_room_name,
+                'iot': iot_data,
+                'roomId': room_id,
+                'familyId': family_id
+            }
+            family_device_qs = FamilyRoomDevice.objects.filter(device_id=device_id)
+            family_device_qs = family_device_qs.filter(~Q(sub_device=0)).order_by('-created_time')
+
+            sub_device = []
+            if family_device_qs.exists():
+                family_device_qs = family_device_qs.values()
+                for item in family_device_qs:
+                    sub_id = item['sub_device']
+                    gateway_sub_qs = GatewaySubDevice.objects.filter(device_id=device_id, id=sub_id).values(
+                        'id', 'device_type',
+                        'nickname',
+                        'src_addr', 'status',
+                        'created_time', 'ieee_addr')
+                    if not gateway_sub_qs.exists():
+                        continue
+                    room_id = item['room_id']
+                    room_qs = FamilyRoom.objects.filter(id=room_id)
+                    gateway_room_name = room_qs.first().name if room_qs.exists() else ''
+                    gateway_sub_qs = gateway_sub_qs.first()
+                    sub_device.append({
+                        'gatewaySubId': gateway_sub_qs['id'],
+                        'nickName': gateway_sub_qs['nickname'],
+                        'deviceType': gateway_sub_qs['device_type'],
+                        'srcAddr': gateway_sub_qs['src_addr'],
+                        'status': gateway_sub_qs['status'],
+                        'createdTime': gateway_sub_qs['created_time'],
+                        'roomName': gateway_room_name,
+                        'roomId': room_qs.first().id if room_qs.exists() else 0,
+                        'ieeeAddr': gateway_sub_qs['ieee_addr'],
+                        'familyId': family_id,
+                    })
+            res = {'gateway': gateway, 'sub_device': sub_device, 'sub_device_count': len(sub_device), 'scene_count': 0}
+            return response.json(0, res)
+
+        except Exception as e:
+            print(e.args)
+            return response.json(500)

+ 551 - 0
Controller/SensorGateway/GatewayFamilyMemberController.py

@@ -0,0 +1,551 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : GatewayFamilyMemberController.py
+@Time    : 2022/5/25 10:21
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+
+import json
+import time
+
+from django.db import transaction
+from django.views.generic.base import View
+
+from Controller.SensorGateway.EquipmentFamilyController import EquipmentFamilyView
+from Model.models import UserFamily, FamilyMemberJoin, FamilyMember, SysMsgModel, FamilyMemberPermission, \
+    Device_User, FamilyRoomDevice, FamilyRoom
+from Object.RedisObject import RedisObject
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from Service.CommonService import CommonService
+
+
+# 家庭房间管理
+class GatewayFamilyMemberView(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 validation(self, request_dict, request, operation):
+        token = request.META.get('HTTP_AUTHORIZATION')
+        token = TokenObject(token)
+        lang = request_dict.get('lang', None)
+        response = ResponseObject(lang) if lang else ResponseObject(token.lang)
+        if token.code != 0:
+            return response.json(token.code)
+        app_user_id = token.userID
+        # 添加设备关联房间
+        if operation == 'join':
+            return self.member_join(app_user_id, request_dict, response)
+        elif operation == 'join/page':
+            return self.member_join_page(request_dict, response)
+        elif operation == 'qrcode/generate':
+            return self.family_generate_token(app_user_id, request_dict, response)
+        elif operation == 'qrcode/check':
+            return self.check_qrcode(request_dict, response)
+        elif operation == 'join/info':
+            return self.member_join_info(request_dict, response)
+        elif operation == 'join/update':
+            return self.user_join_confirm(request_dict, response)
+        elif operation == 'del':
+            return self.member_del(app_user_id, request_dict, response)
+        elif operation == 'created':
+            return self.family_created(app_user_id, request_dict, response)
+        elif operation == 'del/home':
+            return self.del_home(app_user_id, request_dict, response)
+        elif operation == 'join/del':
+            return self.member_join_del(app_user_id, request_dict, response)
+
+    @classmethod
+    def member_join_del(cls, app_user_id, request_dict, response):
+        """
+        删除最近联系人
+        @param app_user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        join_ids = request_dict.get('joinIds', None)
+        family_id = request_dict.get('familyId', None)
+        if not join_ids:
+            return response.json()
+        is_owner = EquipmentFamilyView.get_family_owner(app_user_id, family_id)
+        if not is_owner:
+            return response.json(404)
+        try:
+            with transaction.atomic():
+                items = join_ids.split(',')
+                for item in items:
+                    family_member_join_qs = FamilyMemberJoin.objects.filter(id=int(item))
+                    if family_member_join_qs.exists():
+                        family_member_join_qs.delete()
+            return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def del_home(cls, app_user_id, request_dict, response):
+        """
+        删除家庭
+        @param app_user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        if not family_id:
+            return response.json(444)
+        is_owner = EquipmentFamilyView.get_family_owner(app_user_id, family_id)
+        if not is_owner:
+            return response.json(404)
+        family_id = int(family_id)
+        try:
+            with transaction.atomic():
+                family_member_qs = FamilyMember.objects.filter(family_id=family_id, identity=0)
+                if family_member_qs.exists():
+                    return response.json(10057)
+                family_room_dev_qs = FamilyRoomDevice.objects.filter(family_id=family_id)
+                if family_room_dev_qs.exists():
+                    return response.json(10057)
+                family_room_qs = FamilyRoom.objects.filter(family_id=family_id)
+                if family_room_qs.exists():
+                    family_room_qs.delete()
+                FamilyMember.objects.filter(family_id=family_id).delete()
+                user_family_qs = UserFamily.objects.filter(id=family_id)
+                if user_family_qs.exists():
+                    user_family_qs.delete()
+                    return response.json(0)
+            return response.json(176)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def family_created(cls, app_user_id, request_dict, response):
+        """
+        创建家庭
+        @param app_user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        family_name = request_dict.get('familyName', None)
+        if not family_name:
+            return response.json(444)
+        n_time = int(time.time())
+        location = request_dict.get('location', '')
+        room_names = request_dict.get('roomNames', None)
+        user_family_qs = UserFamily.objects.filter(name=family_name, user_id=app_user_id)
+        if user_family_qs.exists():
+            return response.json(174)
+        try:
+            with transaction.atomic():
+                member_permission_qs = FamilyMemberPermission.objects.filter(no='001').values('id')
+                user_family = UserFamily.objects.create(user_id=app_user_id, name=family_name,
+                                                        location=location,
+                                                        updated_time=n_time,
+                                                        created_time=n_time)
+                if user_family.id:
+                    device_user = Device_User.objects.get(userID=app_user_id)
+                    permission_id = member_permission_qs.first()['id']
+                    FamilyMember.objects.create(family_id=user_family.id, user_id=app_user_id,
+                                                user_name=device_user.username, identity=1,
+                                                permission_id=int(permission_id), sort=1, updated_time=n_time,
+                                                created_time=n_time)
+                if room_names:
+                    names = room_names.split(',')
+                    rooms = []
+                    for index, item in enumerate(names):
+                        rooms.append(FamilyRoom(
+                            name=item,
+                            family_id=user_family.id,
+                            sort=index,
+                            updated_time=n_time,
+                            created_time=n_time
+                        ))
+                    FamilyRoom.objects.bulk_create(rooms)
+                return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def member_del(cls, app_user_id, request_dict, response):
+        """
+        成员删除或退出
+        @param app_user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        member_id = request_dict.get('memberId', None)
+        family_id = request_dict.get('familyId', None)
+        if not family_id:
+            return response.json(444)
+        try:
+            family_id = int(family_id)
+            with transaction.atomic():
+                # 家庭所有者删除成员
+                if member_id:
+                    is_owner = EquipmentFamilyView.get_family_owner(app_user_id, family_id)
+                    if not is_owner:
+                        return response.json(404)
+                    member_id = int(member_id)
+                    family_member_qs = FamilyMember.objects.filter(id=member_id)
+                    if not family_member_qs.exists():
+                        return response.json(173)
+                    family_member_qs.delete()
+                    return response.json(0)
+                else:
+                    # 成员退出当前家庭
+                    family_member = FamilyMember.objects.filter(family_id=family_id, user_id=app_user_id, identity=1)
+                    if family_member.exists():
+                        return response.json(173)
+                    family_member_qs = FamilyMember.objects.filter(family_id=family_id, user_id=app_user_id)
+                    if not family_member_qs.exists():
+                        return response.json(173)
+                    family_member_qs.delete()
+                    return response.json(0)
+        except Exception as e:
+            return response.json(177, repr(e))
+
+    @classmethod
+    def user_join_confirm(cls, request_dict, response):
+        """
+        用户加入家庭确认
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        join_id = request_dict.get('joinId', None)
+        confirm = request_dict.get('confirm', None)
+        lang = request_dict.get('lang', 'en')
+        if not all([join_id, confirm]):
+            return response.json(444)
+        join_id = int(join_id)
+        family_join = FamilyMemberJoin.objects.filter(id=join_id, status=0)
+        if not family_join.exists():
+            return response.json(173)
+        family_member = FamilyMember.objects.filter(family_id=family_join.first().family_id,
+                                                    user_id=family_join.first().user_id)
+        if family_member.exists():
+            return response.json(174)
+        try:
+            now_time = int(time.time())
+            with transaction.atomic():
+                family_join.update(status=2 if confirm == 'confirm' else 1)
+                family_join = FamilyMemberJoin.objects.filter(id=join_id)
+                family_join = family_join.values('id', 'user_id', 'user__username', 'user__NickName', 'family__name',
+                                                 'family_id', 'family__user_id', 'status', 'type', 'updated_time')
+                family_join = family_join.first()
+                nick_name = family_join['user__NickName']
+                family_name = family_join['family__name']
+                join_type = family_join['type']
+                data = {
+                    'addTime': now_time,
+                    'updTime': now_time,
+                    'userID_id': family_join['user_id'] if join_type == 1 else family_join['family__user_id'],
+                    'eventType': 0
+                }
+                if confirm == 'confirm':
+                    msg_text = cls.get_confirm_text(nick_name, family_name, join_type, True, lang)
+                    p_id = FamilyMemberPermission.objects.filter(no='002').values('id').first()
+                    p_id = p_id['id']
+                    FamilyMember.objects.create(family_id=family_join['family_id'], updated_time=now_time,
+                                                user_id=family_join['user_id'],
+                                                created_time=now_time,
+                                                permission_id=p_id,
+                                                user_name=family_join['user__username'])
+                elif confirm == 'refusal':
+                    msg_text = cls.get_confirm_text(nick_name, family_name, join_type, False, lang)
+                else:
+                    ex = Exception("data is null")
+                    raise ex
+                data['msg'] = msg_text
+                data['uid'] = family_join['id']
+                SysMsgModel.objects.create(**data)
+            return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def get_confirm_text(cls, nick_name, family_name, join_type, is_confirm, lang='en'):
+        # join_type == 1等于扫码请求加u人
+        if is_confirm:
+            if join_type == 1:
+                if lang == 'cn':
+                    msg_text = '{} 同意你的请求'.format(family_name)
+                else:
+                    msg_text = '{} confirm to your request'.format(family_name)
+            else:
+                if lang == 'cn':
+                    msg_text = '{}同意加入{}'.format(nick_name, family_name)
+                else:
+                    msg_text = '{} confirm to join {}'.format(nick_name, family_name)
+        else:
+            if join_type == 1:
+                if lang == 'cn':
+                    msg_text = '{}拒绝你的请求'.format(family_name)
+                else:
+                    msg_text = '{} refused your request'.format(family_name)
+
+            else:
+                if lang == 'cn':
+                    msg_text = '{}拒绝加入{}'.format(nick_name, family_name)
+                else:
+                    msg_text = '{} refused to join '.format(family_name)
+        return msg_text
+
+    @classmethod
+    def member_join_info(cls, request_dict, response):
+        """
+        查询加入家庭信息
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        join_id = request_dict.get('joinId', None)
+        if not join_id:
+            return response.json(444)
+        join_id = int(join_id)
+        family_join_qs = FamilyMemberJoin.objects.filter(id=join_id)
+        if not family_join_qs.exists():
+            return response.json(173)
+        family_join_qs = family_join_qs.values('type', 'user__NickName', 'family__name', 'status', 'id',
+                                               'family__user__NickName')
+        family_join_qs = family_join_qs.first()
+        nick_name = family_join_qs['user__NickName']
+        if family_join_qs['type'] == 0:
+            nick_name = family_join_qs['family__user__NickName']
+        return response.json(0, {
+            'id': family_join_qs['id'],
+            'type': family_join_qs['type'],
+            'nickName': nick_name,
+            'familyName': family_join_qs['family__name'],
+            'status': family_join_qs['status'],
+        })
+
+    @classmethod
+    def member_join(cls, app_user_id, request_dict, response):
+        """
+        家庭成员加入
+        @param app_user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        invite = request_dict.get('QRCodeInvite', 0)
+        user_id = request_dict.get('app_user_id', None)
+        lang = request_dict.get('lang', 'en')
+        if not all([family_id, invite]):
+            return response.json(444)
+        invite = int(invite)
+        family_id = int(family_id)
+        now_time = int(time.time())
+        # 判断当前家庭是否已有该成员
+        if invite == 1:
+            family_qs = FamilyMember.objects.filter(family_id=family_id, user_id=app_user_id)
+        elif user_id:
+            family_qs = FamilyMember.objects.filter(family_id=family_id, user_id=user_id)
+        else:
+            return response.json(173)
+        if family_qs.exists():
+            return response.json(174)
+        try:
+            user_family_qs = UserFamily.objects.filter(id=family_id)
+            if not user_family_qs:
+                return response.json(173)
+            user_family = user_family_qs.first()
+            family_name = user_family.name
+            data = {
+                'addTime': now_time,
+                'updTime': now_time,
+                'eventType': 705
+            }
+            # 请求加入家庭,用户信息
+            user_qs = Device_User.objects.filter(userID=app_user_id)
+            nick_name = user_qs.first().NickName
+
+            if invite == 1:
+                msg_text = cls.get_msg_text(nick_name, family_name, lang, True)
+                # 通知对象 家庭所有者
+                join_user_id = app_user_id
+                data['userID_id'] = user_family.user_id
+            elif app_user_id:
+                msg_text = cls.get_msg_text(nick_name, family_name, lang, False)
+                # 通知对象 被邀请人
+                join_user_id = user_id
+                data['userID_id'] = user_id
+            else:
+                return response.json(173)
+            data['msg'] = msg_text
+            with transaction.atomic():
+                member_user_id = user_id if user_id else app_user_id
+                member_join_qs = FamilyMemberJoin.objects.filter(family_id=family_id, user_id=member_user_id)
+                if member_join_qs.exists():
+                    member_join_qs.update(status=0, updated_time=now_time)
+                    data['uid'] = member_join_qs.first().id
+                    SysMsgModel.objects.create(**data)
+                    return response.json(0)
+                family_user_join = FamilyMemberJoin.objects.create(family_id=family_id, user_id=join_user_id,
+                                                                   status=0,
+                                                                   updated_time=now_time,
+                                                                   created_time=now_time,
+                                                                   type=invite)
+                data['uid'] = family_user_join.id
+                SysMsgModel.objects.create(**data)
+                return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def get_msg_text(cls, nick_name, family_name, lang, is_invite=False):
+        """
+        系统消息通知内容,中英文判断
+        @param nick_name:
+        @param family_name:
+        @param lang:
+        @param is_invite:
+        @return:
+        """
+        if lang and lang == 'cn':
+            if is_invite:
+                msg_text = '{}请求加入你的{}'.format(nick_name, family_name)
+            else:
+                msg_text = '{}邀请你加入{}'.format(nick_name, family_name)
+        else:
+            if is_invite:
+                msg_text = '{} Request to join your Homegroup {}'.format(nick_name, family_name)
+            else:
+                msg_text = '{} invite you to join the Homegroup {}'.format(nick_name, family_name)
+
+        return msg_text
+
+    @classmethod
+    def member_join_page(cls, request_dict, response):
+        """
+        家庭成员邀请记录
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        page_no = request_dict.get('pageNo', None)
+        page_size = request_dict.get('pageSize', None)
+        family_id = request_dict.get('familyId', None)
+        if not all([page_no, page_size, family_id]):
+            return response.json(444)
+        member_join = FamilyMemberJoin.objects.filter(family_id=family_id).values('id', 'status', 'user__username',
+                                                                                  'user__userIconUrl', 'user__phone',
+                                                                                  'user__userEmail', 'updated_time',
+                                                                                  'created_time',
+                                                                                  'user__NickName')
+
+        page_no = int(page_no)
+        page_size = int(page_size)
+        member_join = member_join.order_by('-updated_time')[(page_no - 1) * page_size: page_no * page_size]
+        if not member_join.exists():
+            return response.json(0, [])
+        result = []
+        for item in member_join:
+            result.append({
+                'joinId': item['id'],
+                'status': item['status'],
+                'userName': item['user__username'],
+                'userIconUrl': item['user__userIconUrl'],
+                'phone': item['user__phone'],
+                'nickName': item['user__NickName'],
+                'userEmail': item['user__userEmail'],
+                'updatedTime': item['updated_time'],
+                'createdTime': item['created_time']
+            })
+        return response.json(0, result)
+
+    @classmethod
+    def family_generate_token(cls, app_user_id, request_dict, response):
+        """
+        获取家庭令牌信息
+        @param app_user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        if not family_id:
+            return response.json(444)
+        family_id = int(family_id)
+        family_qs = UserFamily.objects.filter(id=family_id, user_id=app_user_id) \
+            .values('name', 'user__userEmail',
+                    'user__userIconUrl',
+                    'user__phone', 'user__NickName',
+                    'user__username')
+        if not family_qs.exists():
+            return response.json(173)
+        family_qs = family_qs.first()
+        data = {'sharer': app_user_id, 'familyId': family_id}
+        # -----------------
+        # share_token = jwt.encode(data, '.#^^&', algorithm='HS256').decode('utf-8')
+        # +++++++++++++++++
+        redisObj = RedisObject()
+        share_token = str(int(time.time())) + CommonService.RandomStr(22)
+        if not redisObj.set_data(key=share_token, val=json.dumps(data), expire=300):
+            return response.json(10, '生成失败')
+        return response.json(0, {
+            'shareToken': share_token,
+            'userPhone': family_qs['user__phone'],
+            'nickName': family_qs['user__NickName'],
+            'userIconUrl': family_qs['user__userIconUrl'],
+            'userName': family_qs['user__username'],
+            'familyName': family_qs['name'],
+            'userEmail': family_qs['user__userEmail'],
+        })
+
+    @classmethod
+    def check_qrcode(cls, request_dict, response):
+        """
+        检查二维码是否过期
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        share_token = request_dict.get('shareToken', None)
+        if not share_token:
+            return response.json(444)
+        redisObj = RedisObject()
+        share_data = redisObj.get_data(key=share_token)
+        if not share_data:
+            return response.json(119)
+        data = json.loads(share_data)
+        sharer = data.get('sharer', None)
+        family_id = data.get('familyId', None)
+        if not all([sharer, family_id]):
+            return response.json(173)
+        family_id = int(family_id)
+        user_family_qs = UserFamily.objects.filter(id=family_id)
+        if not user_family_qs.exists():
+            return response.json(173)
+        user_family_qs = user_family_qs.values('id', 'name', 'user__userEmail',
+                                               'user__userIconUrl',
+                                               'user__phone', 'user__NickName',
+                                               'user__username')
+        return response.json(0, {
+            'familyId': user_family_qs[0]['id'],
+            'userPhone': user_family_qs[0]['user__phone'],
+            'nickName': user_family_qs[0]['user__NickName'],
+            'userIconUrl': user_family_qs[0]['user__userIconUrl'],
+            'userName': user_family_qs[0]['user__username'],
+            'familyName': user_family_qs[0]['name'],
+            'userEmail': user_family_qs[0]['user__userEmail'],
+        })

+ 253 - 0
Controller/SensorGateway/GatewayFamilyRoomController.py

@@ -0,0 +1,253 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : GatewayFamilyRoomController.py
+@Time    : 2022/5/24 19:43
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+
+from django.db import transaction
+from django.db.models import Q, Count, F
+from django.views.generic.base import View
+
+from Controller.SensorGateway.EquipmentFamilyController import EquipmentFamilyView
+from Model.models import FamilyRoomDevice, FamilyRoom
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+
+
+# 家庭房间管理
+class GatewayFamilyRoomView(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 validation(self, request_dict, request, operation):
+        token = request.META.get('HTTP_AUTHORIZATION')
+        token = TokenObject(token)
+        lang = request_dict.get('lang', token.lang)
+        response = ResponseObject(lang)
+        if token.code != 0:
+            return response.json(token.code)
+        app_user_id = token.userID
+        # 添加设备关联房间
+        if operation == 'device-changes':
+            return self.room_device_save(app_user_id, request_dict, response)
+        # 房间排序
+        elif operation == 'sort':
+            return self.room_sort_save(request_dict, response)
+        # 房间删除
+        elif operation == 'del':
+            return self.room_del(app_user_id, request_dict, response)
+        # 房间详情
+        elif operation == 'details':
+            return self.get_room_details(app_user_id, request_dict, response)
+        elif operation == 'all-devices':    # 家庭所有设备
+            return self.all_devices(request_dict, response)
+        elif operation == 'devices-sort':    # 家庭设备排序
+            return self.devices_sort(request_dict, response)
+        else:
+            return response.json(414)
+
+    @classmethod
+    def room_device_save(cls, app_user_id, request_dict, response):
+        """
+        房间加入设备or移除设备
+        @param app_user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        device_ids = request_dict.get('deviceIds', None)
+        room_id = request_dict.get('roomId', None)
+        if not all([family_id, room_id]):
+            return response.json(444)
+        family_id = int(family_id)
+        room_id = int(room_id)
+        is_owner = EquipmentFamilyView.get_family_owner(app_user_id, family_id)
+        if not is_owner:
+            return response.json(404)
+        try:
+            with transaction.atomic():
+                room_qs = FamilyRoom.objects.filter(family_id=family_id, id=room_id)
+                if not room_qs.exists():
+                    return response.json(173)
+                qs = FamilyRoomDevice.objects.filter(family_id=family_id, room_id=room_id)
+                if qs.exists():
+                    qs.update(room_id=0, sort=0)
+                if device_ids:
+                    device_ids = device_ids.split(',')
+                    for i, item in enumerate(device_ids):
+                        device_id = int(item)
+                        device_qs = FamilyRoomDevice.objects.filter(device_id=device_id)
+                        if device_qs.exists():
+                            device_qs.update(room_id=room_id, sort=i)
+                return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def room_del(cls, user_id, request_dict, response):
+        """
+        房间多选删除
+        @param user_id: 当前登录用户id
+        @param request_dict: 请求参数
+        @param response: 响应参数
+        @return:
+        """
+        ids = request_dict.get('roomIds', None)
+        if not ids:
+            return response.json(444)
+        ids = ids.split(',')
+        room_id = ids[0]
+        room_info = FamilyRoom.objects.filter(id=room_id)
+        if not room_info.exists():
+            return response.json(173)
+        is_owner = EquipmentFamilyView.get_family_owner(user_id, room_info.first().family_id)
+        if not is_owner:
+            return response.json(404)
+        try:
+            with transaction.atomic():
+                for item in ids:
+                    room_id = int(item)
+                    room_device = FamilyRoomDevice.objects.filter(room_id=room_id)
+                    if room_device.exists():
+                        room_device.update(room_id=0)
+                    FamilyRoom.objects.filter(id=room_id).delete()
+                return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def room_sort_save(cls, request_dict, response):
+        """
+        房间排序
+        @param request_dict: 请求参数
+        @param response: 响应参数
+        @return:
+        """
+        ids = request_dict.get('ids', None)
+        if not ids:
+            return response.json(444)
+        items = ids.split(',')
+        for item in items:
+            item = item.split('-')
+            room_id, sort = item[0], item[1]
+            family_room = FamilyRoom.objects.filter(id=int(room_id))
+            if family_room.exists():
+                family_room.update(sort=int(sort))
+        return response.json(0)
+
+    @classmethod
+    def get_room_details(cls, app_user_id, request_dict, response):
+        """
+        房间设备详情(所在当前房间下,和所在家庭不在当前房间下的主设备)
+        @param app_user_id:
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        room_id = request_dict.get('roomId', None)
+        if not all([family_id, room_id]):
+            return response.json(444)
+        is_owner = EquipmentFamilyView.get_family_owner(app_user_id, family_id)
+        if not is_owner:
+            return response.json(404)
+        family_id = int(family_id)
+        room_id = int(room_id)
+        room_device_qs = FamilyRoomDevice.objects.filter(family_id=family_id, room_id=room_id).order_by('sort').values(
+            'device_id').annotate(count=Count('device_id')).values('device_id', 'device__Type', 'device__NickName')
+        device_room = []
+        if room_device_qs.exists():
+            room_name = FamilyRoom.objects.filter(id=room_id)
+            for item in room_device_qs:
+                device_room.append({
+                    'deviceId': item['device_id'],
+                    'deviceType': item['device__Type'],
+                    'nickName': item['device__NickName'],
+                    'roomName': room_name.first().name if room_name.exists() else '',
+                })
+
+        device_not_room = []
+        device_not_room_qs = FamilyRoomDevice.objects.filter(family_id=family_id)
+        device_not_room_qs = device_not_room_qs.filter(~Q(room_id=room_id)).values('device_id').annotate(
+            count=Count('device_id')).values('room_id', 'device_id', 'device__Type', 'device__NickName')
+        if device_not_room_qs.exists():
+            for item in device_not_room_qs:
+                name = ''
+                if room_device_qs.exists():
+                    family_room_qs = FamilyRoom.objects.filter(id=item['room_id'])
+                    if family_room_qs.exists():
+                        name = family_room_qs.first().name
+                device_not_room.append({
+                    'deviceId': item['device_id'],
+                    'deviceType': item['device__Type'],
+                    'nickName': item['device__NickName'],
+                    'roomName': name
+                })
+        return response.json(0, {'deviceRooms': device_room, 'deviceNotRooms': device_not_room})
+
+    @staticmethod
+    def all_devices(request_dict, response):
+        """
+        家庭所有设备(网关和摄像头设备)
+        @param request_dict: 请求参数
+        @request_dict familyId: 家庭id
+        @param response: 响应参数
+        @return:
+        """
+        family_id = request_dict.get('familyId', None)
+        if not family_id:
+            return response.json(444)
+        try:
+            family_room_device_qs = FamilyRoomDevice.objects.filter(family_id=family_id, sub_device=0).\
+                annotate(type=F('device__Type'), nickname=F('device__NickName')).\
+                values('device_id', 'type', 'nickname', 'room_id').order_by('sort')
+            if not family_room_device_qs.exists():
+                return response.json(0, [])
+            # 查询房间名称
+            for device in family_room_device_qs:
+                room_id = device['room_id']
+                if room_id == 0:
+                    device['room'] = ''
+                else:
+                    family_room_qs = FamilyRoom.objects.filter(id=room_id).values('name')
+                    device['room'] = family_room_qs[0]['name'] if family_room_qs.exists() else ''
+                device.pop('room_id')
+            return response.json(0, list(family_room_device_qs))
+        except Exception as e:
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def devices_sort(request_dict, response):
+        """
+        家庭设备排序
+        @param request_dict: 请求参数
+        @request_dict 设备id: deviceIds
+        @param response: 响应参数
+        @return:
+        """
+        device_ids = request_dict.get('deviceIds', None)
+        if not device_ids:
+            return response.json(444)
+        try:
+            device_ids = device_ids.split(',')
+            with transaction.atomic():
+                for i, item in enumerate(device_ids):
+                    device_id = int(item)
+                    FamilyRoomDevice.objects.filter(device_id=device_id).update(sort=i)
+            return response.json(0)
+        except Exception as e:
+            return response.json(500, repr(e))

+ 0 - 0
SensorGateway/SensorGatewayController.py → Controller/SensorGateway/SensorGatewayController.py


+ 362 - 0
Controller/SensorGateway/SubDeviceController.py

@@ -0,0 +1,362 @@
+# -*- coding: utf-8 -*-
+"""
+@Author : Rocky
+@Time : 2022/5/25 15:17
+@File :SubDeviceController.py
+"""
+import time
+from collections import OrderedDict
+
+from django.db import transaction
+from django.db.models import Count
+from django.views import View
+
+from Model.models import Device_Info, GatewaySubDevice, FamilyRoomDevice, SensorRecord
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from Service.CommonService import CommonService
+
+
+class GatewaySubDeviceView(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 validation(self, request_dict, request, operation):
+        token_obj = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
+        lang = request_dict.get('lang', None)
+        response = ResponseObject(lang if lang else token_obj.lang)
+
+        if token_obj.code != 0:
+            return response.json(token_obj.code)
+        user_id = token_obj.userID
+        if operation == 'add':  # 添加子设备
+            return self.add_sub_device(request_dict, user_id, response)
+        elif operation == 'detail':  # 查询子设备信息
+            return self.query_sub_device(request_dict, response)
+        elif operation == 'update':  # 更新子设备信息
+            return self.sensor_update(request_dict, response)
+        elif operation == 'delete':  # 删除子设备
+            return self.delete(request_dict, user_id, response)
+        elif operation == 'records/tem-hum':  # 查询温湿度传感器记录
+            return self.records_tem_hum(request_dict, response)
+        elif operation == 'records':  # 查询其他传感器记录
+            return self.records(request_dict, response)
+        elif operation == 'records-date':  # 查询传感器记录日期
+            return self.records_date(request_dict, response)
+        else:
+            return response.json(414)
+
+    @staticmethod
+    def add_sub_device(request_dict, user_id, response):
+        """
+        添加子设备
+        @param request_dict: 请求参数
+        @request_dict serialNumber: 序列号
+        @request_dict deviceType: 设备类型
+        @request_dict nickName: 设备名
+        @request_dict ieeeAddr: 长地址
+        @request_dict srcAddr: 短地址
+        @request_dict mac: mac地址
+        @request_dict deviceModel: 设备型号
+        @request_dict manufacturer: 制造商
+        @request_dict sensorSerial: 传感器序列号
+        @request_dict firmwareVersion: 固件版本
+        @request_dict hardwareVersion: 硬件版本
+        @request_dict familyId: 家庭id
+        @request_dict roomId: 房间id
+        @param user_id: 用户id
+        @param response: 响应对象
+        @return: response 响应对象
+        """
+        serial_number = request_dict.get('serialNumber', None)
+        device_type = int(request_dict.get('deviceType', None))
+        nickname = request_dict.get('nickName', None)
+        ieee_addr = request_dict.get('ieeeAddr', None)
+        src_addr = request_dict.get('srcAddr', None)
+        mac = request_dict.get('mac', '')
+        device_model = request_dict.get('deviceModel', '')
+        manufacturer = request_dict.get('manufacturer', '')
+        sensor_serial = request_dict.get('sensorSerial', '')
+        firmware_version = request_dict.get('firmwareVersion', '')
+        hardware_version = request_dict.get('hardwareVersion', '')
+
+        family_id = request_dict.get('familyId', None)
+        room_id = request_dict.get('roomId', 0)
+
+        if not all([serial_number, device_type, nickname, src_addr, family_id]):
+            return response.json(444)
+        now_time = int(time.time())
+        try:
+            # 查询网关设备数据
+            device_info_qs = Device_Info.objects.filter(userID_id=user_id, serial_number=serial_number).values('id')
+            if not device_info_qs.exists():
+                return response.json(14)
+            device_id = device_info_qs[0]['id']
+            # 查询是否已添加过该子设备
+            sub_device_qs = GatewaySubDevice.objects.filter(device_id=device_id, ieee_addr=ieee_addr)
+            if sub_device_qs.exists():
+                return response.json(174)
+
+            with transaction.atomic():
+                sub_device = GatewaySubDevice.objects.create(device_id=device_id, device_type=device_type,
+                                                             nickname=nickname, ieee_addr=ieee_addr, src_addr=src_addr,
+                                                             status=1, mac=mac, device_model=device_model,
+                                                             manufacturer=manufacturer, sensor_serial=sensor_serial,
+                                                             firmware_version=firmware_version,
+                                                             hardware_version=hardware_version,
+                                                             created_time=now_time, updated_time=now_time)
+                FamilyRoomDevice.objects.create(family_id=family_id, room_id=room_id, device_id=device_id,
+                                                sub_device=sub_device.id, created_time=now_time, updated_time=now_time)
+            return response.json(0)
+        except Exception as e:
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def query_sub_device(request_dict, response):
+        """
+        查询子设备信息
+        @param request_dict: 请求参数
+        @request_dict gatewaySubId: 子设备id
+        @param response: 响应对象
+        @return: response
+        """
+        sub_device_id = request_dict.get('gatewaySubId', None)
+
+        if not all([sub_device_id]):
+            return response.json(444)
+        try:
+            gateway_sub_device_qs = GatewaySubDevice.objects.filter(id=sub_device_id).values('manufacturer',
+                                                                                             'device_model',
+                                                                                             'mac', 'sensor_serial')
+            if not gateway_sub_device_qs.exists():
+                return response.json(173)
+            res = {
+                'manufacturer': gateway_sub_device_qs[0]['manufacturer'],
+                'device_model': gateway_sub_device_qs[0]['device_model'],
+                'mac': gateway_sub_device_qs[0]['mac'],
+                'sensor_serial': gateway_sub_device_qs[0]['sensor_serial'],
+            }
+            return response.json(0, res)
+        except Exception as e:
+            return response.json(500, repr(e))
+
+    @classmethod
+    def sensor_update(cls, request_dict, response):
+        """
+        传感器修改
+        @param request_dict: 请求参数
+        @request_dict gateway_sub_id: 子设备id
+        @request_dict nickName: 设备名
+        @request_dict room_id: 房间id
+        @param response: 响应结果
+        @return: list
+        """
+        gateway_sub_id = int(request_dict.get('gatewaySubId', None))
+        nickName = request_dict.get('nickName', None)
+        room_id = request_dict.get('roomId', None)
+        if not all([gateway_sub_id]):
+            return response.json(444)
+        try:
+            gateway_sub_qs = GatewaySubDevice.objects.filter(id=gateway_sub_id)
+            if not gateway_sub_qs.exists():
+                return response.json(173)
+            if nickName:
+                gateway_sub_qs.update(nickname=nickName)
+            if room_id:
+                FamilyRoomDevice.objects.filter(sub_device=gateway_sub_id).update(room_id=room_id)
+            return response.json(0)
+        except Exception as e:
+            print(e.args)
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def delete(request_dict, user_id, response):
+        """
+        更新子设备信息
+        @param request_dict: 请求参数
+        @request_dict sub_device_id: 子设备id
+        @param user_id: 用户id
+        @param response: 响应对象
+        @return: response
+        """
+        sub_device_id = request_dict.get('subDeviceId', None)
+
+        if not all([sub_device_id]):
+            return response.json(444)
+        try:
+            GatewaySubDevice.objects.filter(id=sub_device_id).delete()
+            return response.json(0)
+        except Exception as e:
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def records_tem_hum(request_dict, response):
+        """
+        查询温湿度传感器记录
+        @param request_dict: 请求参数
+        @request_dict gatewaySubId: 子设备id
+        @request_dict cycle: 时间周期
+        @request_dict eventType: 事件类型, 18:温度,19:湿度
+        @param response: 响应对象
+        @return: response
+        """
+        sub_device_id = request_dict.get('gatewaySubId', None)
+        cycle = request_dict.get('cycle', None)
+        event_type = request_dict.get('eventType', None)
+        if not all([sub_device_id, cycle, event_type]):
+            return response.json(444, {'error param': 'gatewaySubId or cycle or eventType'})
+        now_time = int(time.time())
+        try:
+            record_dict = OrderedDict()
+            record_list = []
+            if cycle == 'Hours':
+                start_time = now_time - 24 * 60 * 60
+                sensor_record_qs = SensorRecord.objects.filter(gateway_sub_device_id=sub_device_id,
+                                                               event_type=event_type,
+                                                               created_time__range=(start_time, now_time)). \
+                    values('alarm', 'created_time').order_by('created_time')
+                if not sensor_record_qs.exists():
+                    return response.json(0, {'records': [], 'time': now_time})
+
+                for sensor_record in sensor_record_qs:
+                    created_time = time.strftime('%m/%d %H:%M %w', time.localtime(sensor_record['created_time']))
+                    hour = int(created_time[-7:-5])
+                    minute = int(created_time[-4:-2])
+                    if hour != 23 and minute > 30:  # 不为23时且分钟大于30,hour+1
+                        hour += 1
+                    alarm = float(sensor_record['alarm'])
+                    # 组织数据,record_dict:{"0": [1.0, 2.0, 3.0], "1": [1.0, 2.0, 3.0]...}
+                    if str(hour) in record_dict:
+                        record_dict[str(hour)].append(alarm)
+                    else:
+                        record_dict[str(hour)] = [alarm]
+
+            elif cycle == 'Week':
+                start_time = now_time - 24 * 60 * 60 * 7
+                sensor_record_qs = SensorRecord.objects.filter(gateway_sub_device_id=sub_device_id,
+                                                               event_type=event_type,
+                                                               created_time__range=(start_time, now_time)). \
+                    values('alarm', 'created_time').order_by('created_time')
+                if not sensor_record_qs.exists():
+                    return response.json(0, {'records': [], 'time': now_time})
+
+                for sensor_record in sensor_record_qs:
+                    created_time = time.strftime('%m/%d %H:%M %w', time.localtime(sensor_record['created_time']))
+                    week = int(created_time[-1:])
+                    alarm = float(sensor_record['alarm'])
+                    # 组织数据,record_dict:{"0": [1.0, 2.0, 3.0], "1": [1.0, 2.0, 3.0]...}
+                    if str(week) in record_dict:
+                        record_dict[str(week)].append(alarm)
+                    else:
+                        record_dict[str(week)] = [alarm]
+
+            elif cycle == 'Month':
+                start_time = now_time - 24 * 60 * 60 * 30
+                sensor_record_qs = SensorRecord.objects.filter(gateway_sub_device_id=sub_device_id,
+                                                               event_type=event_type,
+                                                               created_time__range=(start_time, now_time)). \
+                    values('alarm', 'created_time').order_by('created_time')
+                if not sensor_record_qs.exists():
+                    return response.json(0, {'records': [], 'time': now_time})
+
+                for sensor_record in sensor_record_qs:
+                    created_time = time.strftime('%m/%d %H:%M %w', time.localtime(sensor_record['created_time']))
+                    month = int(created_time[:2])
+                    day = int(created_time[3:5])
+                    date = str(month) + '/' + str(day)
+                    alarm = float(sensor_record['alarm'])
+                    # 组织数据,record_dict:{"0": [1.0, 2.0, 3.0], "1": [1.0, 2.0, 3.0]...}
+                    if date in record_dict:
+                        record_dict[date].append(alarm)
+                    else:
+                        record_dict[date] = [alarm]
+
+            # 组织响应数据列表,value为每 小时/天 的平均值
+            for k, v in record_dict.items():
+                record_list.append({'key': k, 'value': round(sum(v) / len(v), 1)})
+
+            res = {
+                'records': record_list,
+                'time': now_time
+            }
+            return response.json(0, res)
+        except Exception as e:
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def records(request_dict, response):
+        """
+        查询其他传感器记录
+        @param request_dict: 请求参数
+        @request_dict gatewaySubId: 子设备id
+        @request_dict page: 页数
+        @request_dict size: 条数
+        @request_dict startTime: 开始时间
+        @request_dict endTime: 结束时间
+        @param response: 响应对象
+        @return: response
+        """
+        sub_device_id = request_dict.get('gatewaySubId', None)
+        page = request_dict.get('page', None)
+        size = request_dict.get('size', None)
+        if not all([sub_device_id, page, size]):
+            return response.json(444, {'error param': 'gatewaySubId or page or size'})
+
+        start_time = request_dict.get('startTime', None)
+        end_time = request_dict.get('endTime', None)
+
+        try:
+            page, size = int(page), int(size)
+            if start_time and end_time:
+                sensor_record_qs = SensorRecord.objects.filter(gateway_sub_device_id=sub_device_id,
+                                                               created_time__range=(start_time, end_time)). \
+                                       values('alarm', 'created_time').order_by('-created_time')[
+                                   (page - 1) * size:page * size]
+            else:
+                sensor_record_qs = SensorRecord.objects.filter(gateway_sub_device_id=sub_device_id). \
+                                       values('alarm', 'created_time').order_by('-created_time')[
+                                   (page - 1) * size:page * size]
+            if not sensor_record_qs.exists():
+                return response.json(0, [])
+            return response.json(0, list(sensor_record_qs))
+        except Exception as e:
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def records_date(request_dict, response):
+        """
+        查询传感器记录日期
+        @param request_dict: 请求参数
+        @request_dict gatewaySubId: 子设备id
+        @request_dict startTime: 开始时间
+        @request_dict endTime: 结束时间
+        @param response: 响应对象
+        @return: response
+        """
+        sub_device_id = request_dict.get('gatewaySubId', None)
+        if not sub_device_id:
+            return response.json(444, {'error param': 'gatewaySubId'})
+
+        try:
+            sensor_record_qs = SensorRecord.objects.extra(
+                select={'date': "FROM_UNIXTIME(created_time,'%%Y-%%m-%%d')"}).values('date'). \
+                                   filter(gateway_sub_device_id=sub_device_id). \
+                                   annotate(count=Count('created_time')). \
+                                   order_by('-date')[:31]
+            record_date_list = []
+            for sensor_record in sensor_record_qs:
+                record_date_list.append({
+                    'timestamp': CommonService.str_to_timestamp(sensor_record['date'], '%Y-%m-%d'),
+                    'count': sensor_record['count'],
+                    'format': sensor_record['date'],
+                })
+            return response.json(0, record_date_list)
+        except Exception as e:
+            return response.json(500, repr(e))

+ 41 - 43
Controller/SerialNumberController.py

@@ -2,27 +2,24 @@
 # -*- coding: utf-8 -*-
 import json
 import logging
-import random
-import requests
 import time
 
+import requests
 from django.db import transaction
 from django.views import View
 
-from Controller.DetectController import DetectControllerView
-from Controller.DeviceConfirmRegion import Device_Region
-from Model.models import SerialNumberModel, CompanySerialModel, UIDCompanySerialModel, CompanyModel, RegionModel, \
-    CountryModel, UIDModel, Device_Info, iotdeviceInfoModel, UidPushModel, LogModel, MacModel, UidSetModel, UID_Bucket, \
-    Unused_Uid_Meal, Order_Model, StsCrdModel, VodHlsModel, ExperienceContextModel, Equipment_Info, UidUserModel
+from Ansjer.config import CRCKey, SERVER_DOMAIN_US, SERVER_DOMAIN_CN, CONFIG_INFO, CONFIG_TEST, CONFIG_US, \
+    CONFIG_CN
+from Model.models import SerialNumberModel, CompanySerialModel, UIDCompanySerialModel, UIDModel, Device_Info, \
+    iotdeviceInfoModel, LogModel, UidSetModel, UID_Bucket, \
+    Unused_Uid_Meal, Order_Model, StsCrdModel, VodHlsModel, ExperienceContextModel, UidUserModel, ExperienceAiModel, \
+    AiService
 from Object.RedisObject import RedisObject
-from Object.uidManageResponseObject import uidManageResponseObject
 from Object.TokenObject import TokenObject
+from Object.uidManageResponseObject import uidManageResponseObject
 from Service.AlgorithmService import AlgorithmBaseOn35
 from Service.CommonService import CommonService
-from Ansjer.config import CRCKey, SERVER_TYPE, SERVER_DOMAIN_US, SERVER_DOMAIN_CN
 from Service.EquipmentInfoService import EquipmentInfoService
-from Service.ModelService import ModelService
-from Object.AWS.S3Email import S3Email
 
 
 class SerialNumberView(View):
@@ -45,9 +42,9 @@ class SerialNumberView(View):
 
         if operation == 'getUID':
             return self.do_get_uid(request_dict, response)
-        elif operation == 'attachUID':      # 绑定uid
+        elif operation == 'attachUID':  # 绑定uid
             return self.do_attach_uid(request_dict, response, request)
-        elif operation == 'detachUID':      # 解绑uid
+        elif operation == 'detachUID':  # 解绑uid
             return self.do_detach_uid(request, request_dict, response)
         elif operation == 'create':
             return self.do_create(request_dict, response)
@@ -133,11 +130,9 @@ class SerialNumberView(View):
 
     def do_attach_uid(self, request_dict, response, request):
         serial_number = request_dict.get('serial_number', None)
-        country_id = request_dict.get('country_id', None)
         company_id = request_dict.get('company_id', None)
         token = request_dict.get('token', None)
         time_stamp = request_dict.get('time_stamp', None)
-        p2p_type = request_dict.get('p2ptype', 1)
 
         if not all([serial_number, company_id, token, time_stamp]):
             return response.json(444)
@@ -150,31 +145,16 @@ class SerialNumberView(View):
         serial = serial_number[0:6]
         full_serial = serial_number[0:9]
 
-        if serial_number[9:10]:
-            p2p_type = serial_number[9:10]
-
         try:
-            if not country_id:
-                # 设备模拟国外环境测试
-                # if SERVER_TYPE == 'Ansjer.us_config.formal_settings':   # 国外正式配置使用固定ip进行测试
-                #     ip = '67.220.90.13'
-                # else:
-                #     ip = CommonService.get_ip_address(request)
-
-                ip = CommonService.get_ip_address(request)
-                country_id = Device_Region().get_device_region(ip)
-
             # 判断序列号是否已和企业关联
             company_serial_qs = CompanySerialModel.objects.filter(company__secret=company_id, serial_number=serial)
             if not company_serial_qs.exists():
                 return response.json(173)
-
-            # 当序列号已关联UID
             company_serial = company_serial_qs[0]
 
             if company_serial.status == 0:  # 该序列号未绑定企业
                 return response.json(173)
-            elif company_serial.status == 1:    # 绑定uid
+            elif company_serial.status == 1:  # 绑定uid
                 # redis加锁,防止同一个序列号重复绑定
                 key = serial + 'do_attach_uid'
                 redisObj = RedisObject()
@@ -182,13 +162,27 @@ class SerialNumberView(View):
                 redisObj.CONN.expire(key, 60)
                 if not isLock:
                     return response.json(5)
+
+                region_id = request_dict.get('region_id', None)
+                if not region_id:
+                    # 根据配置信息确定region_id
+                    region_id = CommonService.confirm_region_id(request)
+
+                p2p_type = request_dict.get('p2ptype', 1)
+                if serial_number[9:10]:
+                    p2p_type = serial_number[9:10]
+                p2p_type = int(p2p_type)
+                # 尚云: 地区为亚洲分配美洲的uid
+                if p2p_type == 1 and region_id == 2:
+                    region_id = 3
+
                 with transaction.atomic():
                     count = 0
                     while count < 3:
                         # 查询是否存在未绑定序列号的uid
                         uid_qs = UIDModel.objects.filter(vpg__company_id=company_serial.company.id,
-                                                         vpg__region_id=country_id, status=0, p2p_type=p2p_type). \
-                                                         order_by('id')
+                                                         vpg__region_id=region_id, status=0, p2p_type=p2p_type). \
+                            order_by('id')
                         if not uid_qs.exists():
                             return response.json(375)
 
@@ -199,7 +193,7 @@ class SerialNumberView(View):
                             return response.json(377)
 
                         result = UIDModel.objects.filter(id=uid.id, status=0).update(status=2, update_time=now_time)
-                        if int(result) <= 0:    # 更新失败
+                        if int(result) <= 0:  # 更新失败
                             count += 1
                             continue
 
@@ -216,7 +210,7 @@ class SerialNumberView(View):
 
                         full_uid_code = uid.full_uid_code
                         if uid.platform in CRCKey.keys():
-                            full_uid_code += ':'+CRCKey[uid.platform]
+                            full_uid_code += ':' + CRCKey[uid.platform]
 
                         res = {
                             'full_uid_code': CommonService.encode_data(full_uid_code),
@@ -249,7 +243,7 @@ class SerialNumberView(View):
                         redisObj.del_data(key=key)
                         return response.json(0, res)
                     return response.json(5)
-            elif company_serial.status == 2:   # 返回uid
+            elif company_serial.status == 2:  # 返回uid
                 uid_qs = UIDCompanySerialModel.objects.filter(company_serial_id=company_serial.id)
                 if not uid_qs.exists():
                     return response.json(173)
@@ -257,7 +251,7 @@ class SerialNumberView(View):
                                     'uid__platform', 'uid__init_string', 'uid__init_string_app')[0]
                 full_uid_code = uid['uid__full_uid_code']
                 if uid['uid__platform'] in CRCKey.keys():
-                    full_uid_code += ':'+CRCKey[uid['uid__platform']]
+                    full_uid_code += ':' + CRCKey[uid['uid__platform']]
                 res = {
                     'full_uid_code': CommonService.encode_data(full_uid_code),
                     'uid': CommonService.encode_data(uid['uid__uid']),
@@ -333,7 +327,7 @@ class SerialNumberView(View):
         try:
             with transaction.atomic():
                 uid = uid_serial.uid.uid
-                company_serial_qs.update(status=1)      # 更新序列号状态
+                company_serial_qs.update(status=1)  # 更新序列号状态
                 # 删除设备相关数据,参考后台的设备重置删除的数据
                 Device_Info.objects.filter(UID=uid).delete()
                 UidSetModel.objects.filter(uid=uid).delete()
@@ -351,6 +345,10 @@ class SerialNumberView(View):
                 VodHlsModel.objects.filter(uid=uid).delete()
                 ExperienceContextModel.objects.filter(uid=uid).delete()
 
+                # 重置AI
+                ExperienceAiModel.objects.filter(uid=uid).delete()
+                AiService.objects.filter(uid=uid).delete()
+
                 # 修改其他数据库的序列号使用状态为已使用
                 update_success = self.update_serial_status(serial=serial, status=1)
                 try:
@@ -358,7 +356,7 @@ class SerialNumberView(View):
                 except AssertionError:
                     return response.json(378)
 
-                UIDModel.objects.filter(uid=uid).update(status=0, mac='')    # 重置uid的使用状态为未使用
+                UIDModel.objects.filter(uid=uid).update(status=0, mac='')  # 重置uid的使用状态为未使用
                 uid_serial.delete()
 
                 # 记录操作日志
@@ -382,11 +380,12 @@ class SerialNumberView(View):
 
     @staticmethod
     def update_serial_status(serial, status):
-        if SERVER_TYPE == 'Ansjer.cn_config.test_settings':   # 测试服不处理
+        if CONFIG_INFO == CONFIG_TEST:  # 测试服不处理
             return True
-        if SERVER_TYPE == 'Ansjer.us_config.formal_settings':
+        server_domain = SERVER_DOMAIN_CN
+        if CONFIG_INFO == CONFIG_US:
             server_domain = SERVER_DOMAIN_CN
-        elif SERVER_TYPE == 'Ansjer.cn_config.formal_settings':
+        elif CONFIG_INFO == CONFIG_CN:
             server_domain = SERVER_DOMAIN_US
         url = server_domain + 'serialNumber/changeSerialNumberStatus'
         data = {'serial': serial, 'status': status}
@@ -434,4 +433,3 @@ class SerialNumberView(View):
             djangoLogger = logging.getLogger('django')
             djangoLogger.exception(repr(e))
             return response.json(500, str(e))
-

+ 74 - 35
Controller/TestApi.py

@@ -56,10 +56,7 @@ from Service.CommonService import CommonService
 from Service.ModelService import ModelService
 from Object.m3u8generate import PlaylistGenerator
 from Model.models import Device_User, Device_Info, UID_Bucket, UID_Preview, UidSetModel, UidPushModel
-from Ansjer.config import PAYPAL_CRD,SERVER_DOMAIN,SERVER_DOMAIN_SSL,PAYPAL_WEB_HOOK_ID
-
-
-
+from Ansjer.config import PAYPAL_CRD, SERVER_DOMAIN, SERVER_DOMAIN_SSL, PAYPAL_WEB_HOOK_ID
 
 SERVER_DOMAIN = 'http://test.dvema.com/'
 ACCESS_KEY = "AKIA2E67UIMD3CYTIWPA"
@@ -86,7 +83,7 @@ class testView(View):
         request.encoding = 'utf-8'
         operation = kwargs.get('operation')
         response = ResponseObject()
-        return response.json(0,request.body)
+        return response.json(0, request.body)
 
     def validation(self, request_dict, request, operation):
         response = ResponseObject()
@@ -97,7 +94,7 @@ class testView(View):
             logger = logging.getLogger('info')
             logger.info('测试打印')
             res = make_password(123456)
-            return JsonResponse(status=200, data=res,safe=False)
+            return JsonResponse(status=200, data=res, safe=False)
         elif operation == 'testMiddleWare':
             a = int('a')
             return JsonResponse(status=200, safe=False)
@@ -126,13 +123,13 @@ class testView(View):
             return self.queryInterface(request_dict, userID, response)
         elif operation == 'generateToken':
             userID = '158943604783713800138000'
-            return self.generate_token(request_dict,userID)
+            return self.generate_token(request_dict, userID)
         elif operation == 'test_upload_s3':
             userID = '158943604783713800138000'
             return self.test_upload_s3(request_dict, response)
         elif operation == 'rekognition':
             userID = '158943604783713800138000'
-            return self.testRekognition(request,request_dict)
+            return self.testRekognition(request, request_dict)
         elif operation == 'ip':
             return self.ip(response)
         elif operation == 'configType':
@@ -141,10 +138,32 @@ class testView(View):
             return self.createData(request_dict, response)
         elif operation == 'findPaypalOrder':
             return self.findPaypalOrder(request_dict, response)
+        elif operation == 'comb':
+            return self.do_comb(request_dict, response)
+        elif operation == 'count_ts':
+            return self.count_ts(request_dict, response)
         else:
             return 123
 
-    def findPaypalOrder(self,request_dict, response):
+    def do_comb(self, request_dict, response):
+        import itertools
+        list1 = [1, 2, 3, 4]
+        list2 = []
+        for i in range(1, len(list1) + 1):
+            iter = itertools.combinations(list1, i)
+            list2.append(list(iter))
+        list3 = []
+        for list_ in list2:
+            for val in list_:
+                print('-------------list')
+                print(type(val))
+                comb = [str(i) for i in val]
+                comb_int = int("".join(comb))
+                list3.append(comb_int)
+
+        return HttpResponse(json.dumps(list3))
+
+    def findPaypalOrder(self, request_dict, response):
         from paypalrestsdk import Order, ResourceNotFound
         PAYPAL_CRD = {
             "mode": "sandbox",  # sandbox or live
@@ -178,11 +197,9 @@ class testView(View):
         # print(related_resources)
         # return HttpResponse(sale)
 
-
-
     def createBucket(self):
 
-        #查看桶列表
+        # 查看桶列表
         # url = "https://azvod1.s3-ap-northeast-1.amazonaws.com"
         # session = Session(ACCESS_KEY, SECRET_KEY)
         # s3_client = session.client('s3', endpoint_url=url)
@@ -193,10 +210,10 @@ class testView(View):
         session = Session(ACCESS_KEY, SECRET_KEY)
         s3_client = session.client('s3')
 
-        #上传
+        # 上传
         # s3_client.put_object(Bucket="azvod1", Key="file/rule.txt", Body=open(r"E:\download\Shadowsocks-4.1.10.0\user-rule.txt", 'rb').read())
 
-        #下载
+        # 下载
         resp = s3_client.get_object(Bucket="azvod1", Key="file/rule.txt")
         with open('local.txt', 'wb') as f:
             f.write(resp['Body'].read())
@@ -219,7 +236,7 @@ class testView(View):
         if uid:
             dvqs = dvqs.filter(UID=uid)
         # count = dvqs.count()
-        #分页
+        # 分页
         dvql = dvqs[(page - 1) * line:page * line].values('id', 'userID', 'NickName', 'UID', 'View_Account',
                                                           'View_Password', 'ChannelIndex', 'Type', 'isShare',
                                                           'primaryUserID', 'primaryMaster', 'data_joined',
@@ -273,8 +290,7 @@ class testView(View):
             data.append(p)
         return response.json(0, data)
 
-
-    #获取播放列表
+    # 获取播放列表
     def do_test_query_vod_list(self, userID, ip, request_dict, response):
         uid = 'GZL2PEFJPLY7W6BG111A'
         channel = 2
@@ -315,7 +331,7 @@ class testView(View):
             'sec': 12})
         return response.json(0, vod_play_list)
 
-    #生成m3u8列表
+    # 生成m3u8列表
     def do_sign_play_m3u8(self, request_dict, response):
         uid = 'GZL2PEFJPLY7W6BG111A'
         channel = 2
@@ -653,9 +669,6 @@ class testView(View):
                 return HttpResponse(json.dumps(res, ensure_ascii=False),
                                     content_type="application/json,charset=utf-8")
 
-
-
-
     def do_pay_by_ali(self, request_dict, userID, response):
         uid = request_dict.get('uid', None)
         rank = request_dict.get('rank', None)
@@ -755,24 +768,23 @@ class testView(View):
             vod_play_list.append({'name': vod['time'], 'sign_url': vod_play_url, 'thumb': thumb, 'sec': vod['sec']})
         return response.json(0, vod_play_list)
 
-    def generate_token(self,request_dict,userID):
+    def generate_token(self, request_dict, userID):
         # UserIdToken
         tko = TokenObject()
-        userID = request_dict.get('userID',None)
-        username = request_dict.get('username',None)
+        userID = request_dict.get('userID', None)
+        username = request_dict.get('username', None)
         res = tko.generate(
             data={'userID': userID, 'lang': 'cn', 'user': username, 'm_code': username})
-        #uidToken
+        # uidToken
         # utko = UidTokenObject()
         # res = utko.generate(data={'uid': '4UZSEDP93MJ3X7YB111A','channel': 1})
 
         # from Object.ETkObject import ETkObject
         # etkObj = ETkObject(etk='')
         # res = etkObj.encrypt("4UZSEDP93MJ3X7YB111A")
-        return JsonResponse(status=200, data=res,safe=False)
-
+        return JsonResponse(status=200, data=res, safe=False)
 
-    def test_upload_s3(self,request_dict , response):
+    def test_upload_s3(self, request_dict, response):
         aws_s3_guonei = boto3.client(
             's3',
             aws_access_key_id=AWS_ACCESS_KEY_ID[0],
@@ -791,8 +803,7 @@ class testView(View):
         )
         return response.json(0, {'datas': response_url, 'count': 1})
 
-
-    def testRekognition(self,request,request_dict):
+    def testRekognition(self, request, request_dict):
         # ip = CommonService.get_ip_address(request)
         # ipInfo = CommonService.getIpIpInfo(ip,"CN")
         # # print(type(ipInfo))
@@ -807,15 +818,15 @@ class testView(View):
         # client = boto3.client('s3', aws_access_key_id='AKIA2E67UIMD45Y3HL53',aws_secret_access_key='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',region_name='us-east-1')
         # exit(request.FILES)
 
-
         files = request.FILES.get('image')
-        labels = int(request_dict.get('labels',5))
-        minConfidence = int(request_dict.get('minConfidence',99))
+        labels = int(request_dict.get('labels', 5))
+        minConfidence = int(request_dict.get('minConfidence', 99))
         if not files:
             return HttpResponse('请上传图片!!!!')
-        client = boto3.client('rekognition', aws_access_key_id='AKIA2E67UIMD6JD6TN3J',aws_secret_access_key='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',region_name='us-east-1')
+        client = boto3.client('rekognition', aws_access_key_id='AKIA2E67UIMD6JD6TN3J',
+                              aws_secret_access_key='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', region_name='us-east-1')
         # image = open('E:/photo/a615fa40b8c476bab0f6eeb332e62a5a-1000.jpg', "rb")
-        response = client.detect_labels(Image={'Bytes':files.read()},MaxLabels=labels,MinConfidence=minConfidence)
+        response = client.detect_labels(Image={'Bytes': files.read()}, MaxLabels=labels, MinConfidence=minConfidence)
         # for obj in response['Labels']:
         #     exit(obj)
         #     if obj['Name'] == 'Person':
@@ -839,3 +850,31 @@ class testView(View):
         # qs = DeviceLogModel.objects.filter(add_time__lt=filter_date)
         DeviceLogModel.objects.create(uid=uid)
         return response.json(0)
+
+    def count_ts(self, request_dict, response):
+        year = request_dict.get('year', None)
+        month = request_dict.get('month', None)
+        if not year:
+            year = CommonService.timestamp_to_str(int(time.time()), '%Y')
+        if not month:
+            month = CommonService.timestamp_to_str(int(time.time()), '%m')
+        year = int(year)
+        month = int(month)
+        startTime = CommonService.str_to_timestamp('{year}-{month}'.format(year=year, month=month), '%Y-%m')
+        endTime = CommonService.str_to_timestamp('{year}-{month}'.format(year=year, month=month + 1), '%Y-%m') - 1
+        qsTs = VodHlsModel.objects.filter(time__gte=startTime, time__lte=endTime).values('fg')
+        if not qsTs.exists():
+            return HttpResponse('查无数据')
+        sumTs = 0  # 总ts个数
+        sumSec = 0  # 总秒数
+        for val in qsTs:
+            fg = int(val['fg'])
+            sumTs += fg & 0xf
+            for i in range(15):
+                shift = (i + 1) * 4
+                sumSec += (fg >> shift) & 0xf
+        size = 0
+        return HttpResponse(
+            "{year}年{month}月 </br>上传的TS总数:{sumTs} </br> 总秒数:{sumSec} </br> 总大小:{size}GB (1秒约等150KB计算)".format(year=year, month=month,
+                                                                                           sumTs=sumTs, sumSec=sumSec,
+                                                                                           size=size))

+ 264 - 0
Controller/UnicomCombo/UnicomComboController.py

@@ -0,0 +1,264 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : UnicomComboController.py
+@Time    : 2022/6/23 9:18
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import json
+import logging
+import time
+
+from django.db import transaction
+from django.http import HttpResponse
+from django.views.generic.base import View
+
+from Model.models import UnicomDeviceInfo, UnicomCombo, Pay_Type
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from Object.UnicomObject import UnicomObjeect
+
+
+class UnicomComboView(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 validation(self, request_dict, request, operation):
+        if operation == 'buy-notify':
+            return self.package_callback_notify(request_dict, request)
+        elif operation == 'device-queue-monitoring':
+            return self.device_queue_monitoring_push(request_dict, request)
+        elif operation == 'device-status-change':
+            return self.device_status_change_push(request_dict, request)
+        elif operation == 'device-bind':
+            response = ResponseObject('cn')
+            return self.device_add(request_dict, response)
+        else:
+            token = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
+            lang = request_dict.get('lang', token.lang)
+            response = ResponseObject(lang)
+            if token.code != 0:
+                return response.json(token.code)
+            if operation == 'combo-save':
+                return self.save_unicom_combo(request_dict, response)
+            elif operation == 'combo-list':
+                return self.query_package_list(response)
+
+    @classmethod
+    def device_add(cls, request_dict, response):
+        """
+        设备绑定iccid
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        iccid = request_dict.get('iccid', None)
+        serial_no = request_dict.get('serialNo', None)
+        if not all([iccid, serial_no]):
+            return response.json(444)
+        n_time = int(time.time())
+        try:
+            with transaction.atomic():
+                # 待完善代码 根据uid与用户id验证系统设备
+                unicom_device_qs = UnicomDeviceInfo.objects.filter(iccid=iccid)
+                if unicom_device_qs.exists():
+                    return response.json(174)
+                unicom_obj = UnicomObjeect()
+                result = unicom_obj.verify_device(iccid=iccid)
+                if result.status_code == 200 and result.text:
+                    res_dict = json.loads(result.text)
+                    if res_dict['success']:
+                        if res_dict['data']['status'] == 0:
+                            return response.json(173)
+                        params = {'iccid': iccid, 'serial_no': serial_no, 'updated_time': n_time,
+                                  'created_time': n_time}
+                        UnicomDeviceInfo.objects.create(**params)
+                    return response.json(0)
+                else:
+                    return response.json(173)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def save_unicom_combo(cls, request_dict, response):
+        """
+        联通套餐保存
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        combo_id = request_dict.get('id', None)
+        combo_name = request_dict.get('comboName', None)
+        flow_total = request_dict.get('flowTotal', None)
+        expiration_days = request_dict.get('expirationDays', None)
+        expiration_type = request_dict.get('expirationType', None)
+        price = request_dict.get('price', None)
+        remark = request_dict.get('remark', None)
+        pay_type = request_dict.get('payType', '').split(',')
+        if not all([pay_type, combo_name, flow_total, expiration_days, expiration_type, price]):
+            return response.json(444)
+        try:
+            flow_total = int(flow_total)
+            expiration_days = int(expiration_days)
+            expiration_type = int(expiration_type)
+            with transaction.atomic():
+                re_data = {
+                    'combo_name': combo_name,
+                    'flow_total': flow_total,
+                    'expiration_days': expiration_days,
+                    'expiration_type': expiration_type,
+                    'price': price,
+                }
+                if remark:
+                    re_data['remark'] = remark
+                if combo_id:
+                    UnicomCombo.objects.filter(id=combo_id).update(**re_data)
+                    UnicomCombo.objects.get(id=combo_id).pay_type.set(pay_type)
+                    return response.json(0)
+                UnicomCombo.objects.create(**re_data).pay_type.set(pay_type)
+                return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def query_package_list(cls, response):
+        """
+        查询套餐列表
+        @return:
+        """
+        try:
+            combo_qs = UnicomCombo.objects.filter(is_show=1, status=0, is_del=False) \
+                .order_by('sort').values('id', 'combo_name',
+                                         'flow_total',
+                                         'expiration_days',
+                                         'expiration_type', 'price',
+                                         'remark')
+            if not combo_qs.exists():
+                return response.json(0, [])
+            combo_list = []
+            for item in combo_qs:
+                # 获取支付方式列表
+                pay_type_qs = Pay_Type.objects.filter(unicomcombo=item['id']).values('id', 'payment')
+                combo_list.append({
+                    'id': item['id'],
+                    'comboName': item['combo_name'],
+                    'flowTotal': item['flow_total'],
+                    'expirationDays': item['expiration_days'],
+                    'expirationType': item['expiration_type'],
+                    'price': item['price'],
+                    'remark': item['remark'],
+                    'payTypes': list(pay_type_qs),
+                })
+            return response.json(0, combo_list)
+        except Exception as e:
+            print(e)
+            return response.json(177, repr(e))
+
+    @classmethod
+    def buy_package(cls):
+        """
+        购买套餐
+        @return:
+        """
+        pass
+
+    @classmethod
+    def query_device_usage_history(cls):
+        """
+        查询用量历史
+        @return:
+        """
+
+    @staticmethod
+    def package_callback_notify(request_dict, request):
+        """
+        异步套餐订购回调
+        @param request_dict:
+        @param request:
+        @return:
+        """
+        logger = logging.getLogger('info')
+        try:
+            logger.info('联通异步套餐订购回调参数{}'.format(request_dict))
+            body = request.body.decode("utf-8")
+            if body:
+                dict_data = json.loads(body)
+                sign = dict_data['sign']
+                logger.info('设备订购异步回调请求参数{}'.format(dict_data))
+                dict_data.pop('sign')
+                unicom_obj = UnicomObjeect()
+                generate_sign = unicom_obj.createSign(**dict_data)
+                logger.info('设备订购请求签名{}'.format(sign))
+                logger.info('设备订购生成签名{}'.format(generate_sign))
+            r_data = {'success': True, 'msg': '成功'}
+            return HttpResponse(json.dumps(r_data, ensure_ascii=False), content_type="application/json,charset=utf-8")
+        except Exception as e:
+            print(repr(e))
+            r_data = {'success': False, 'msg': '失败'}
+            return HttpResponse(json.dumps(r_data, ensure_ascii=False), content_type="application/json,charset=utf-8")
+
+    @staticmethod
+    def device_queue_monitoring_push(request_dict, request):
+        """
+        设备套餐队列用完或者到期推送
+        @param request_dict:
+        @param request:
+        @return:
+        """
+        logger = logging.getLogger('info')
+        try:
+            logger.info('设备套餐队列推送{}'.format(request_dict))
+            body = request.body.decode("utf-8")
+            if body:
+                dict_data = json.loads(body)
+                sign = dict_data['sign']
+                logger.info('设备套餐队列回调请求参数{}'.format(dict_data))
+                dict_data.pop('sign')
+                unicom_obj = UnicomObjeect()
+                generate_sign = unicom_obj.createSign(**dict_data)
+                logger.info('设备套餐队列请求签名{}'.format(sign))
+                logger.info('设备套餐队列生成签名{}'.format(generate_sign))
+            r_data = {'success': True, 'msg': '成功'}
+            return HttpResponse(json.dumps(r_data, ensure_ascii=False), content_type="application/json,charset=utf-8")
+        except Exception as e:
+            print(repr(e))
+            r_data = {'success': False, 'msg': '失败'}
+            return HttpResponse(json.dumps(r_data, ensure_ascii=False), content_type="application/json,charset=utf-8")
+
+    @staticmethod
+    def device_status_change_push(request_dict, request):
+        """
+        设备状态变更推送执行场景说明
+        @param request_dict:
+        @param request:
+        @return:
+        """
+        logger = logging.getLogger('info')
+        try:
+            logger.info('设备状态变更推送{}'.format(request_dict))
+            body = request.body.decode("utf-8")
+            if body:
+                dict_data = json.loads(body)
+                sign = dict_data['sign']
+                logger.info('设备状态变更推送请求参数{}'.format(dict_data))
+                dict_data.pop('sign')
+                unicom_obj = UnicomObjeect()
+                generate_sign = unicom_obj.createSign(**dict_data)
+                logger.info('设备状态变更推送请求签名{}'.format(sign))
+                logger.info('设备状态变更推送生成签名{}'.format(generate_sign))
+            r_data = {'success': True, 'msg': '成功'}
+            return HttpResponse(json.dumps(r_data, ensure_ascii=False), content_type="application/json,charset=utf-8")
+        except Exception as e:
+            print(repr(e))
+            r_data = {'success': False, 'msg': '失败'}
+            return HttpResponse(json.dumps(r_data, ensure_ascii=False), content_type="application/json,charset=utf-8")

+ 1 - 1
Controller/UserBrandController.py

@@ -57,9 +57,9 @@ class MyserviceDynamodb(object):
         try:
             session = Session(aws_access_key_id=self.access_key, aws_secret_access_key=self.secret_key,
                               region_name=self.region)
+            return session
         except:
             print("Failed to connect session in region{0}".format(self.region))
-        return session
 
     # 创建user_brand_all表
     def user_brand_all_table_create(self, table_name):

+ 471 - 71
Model/models.py

@@ -1,13 +1,14 @@
 from itertools import chain
+
 from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
 from django.db import models
-from django.utils import six, timezone
+from django.utils import six
+from django.utils import timezone
 from django.utils.encoding import python_2_unicode_compatible
 from imagekit.models import ProcessedImageField
 from imagekit.processors import ResizeToFill
 
 from Ansjer.config import SERVER_DOMAIN
-from django.utils import timezone
 
 
 class PermissionsManager(models.Manager):
@@ -78,17 +79,19 @@ class Permissions(models.Model):
     def natural_key(self):
         return (self.permName)
 
+
 class MenuModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
     parentId = models.IntegerField(default=0, verbose_name='父节点ID')
-    name = models.CharField(max_length=50,  null=True, default='', verbose_name='名称')   #首字母大写,一定要与vue文件的name对应起来,用于noKeepAlive缓存控制(该项特别重要)
-    path = models.CharField(max_length=100, null=True, default='',verbose_name='路径')
-    component = models.CharField(max_length=100, null=True, default='', verbose_name='vue文件路径') #所谓的vue 组件
+    name = models.CharField(max_length=50, null=True, default='',
+                            verbose_name='名称')  # 首字母大写,一定要与vue文件的name对应起来,用于noKeepAlive缓存控制(该项特别重要)
+    path = models.CharField(max_length=100, null=True, default='', verbose_name='路径')
+    component = models.CharField(max_length=100, null=True, default='', verbose_name='vue文件路径')  # 所谓的vue 组件
     hidden = models.BooleanField(blank=True, default=False, verbose_name=u'是否隐藏')
     alwaysShow = models.BooleanField(blank=True, default=False, verbose_name=u'始终显示当前节点')
     levelHidden = models.BooleanField(blank=True, default=False, verbose_name=u'是否隐藏一级路由')
-    title = models.CharField(max_length=50, default='',verbose_name='标题')
-    icon = models.CharField(max_length=50, default='',verbose_name='图标名')
+    title = models.CharField(max_length=50, default='', verbose_name='标题')
+    icon = models.CharField(max_length=50, default='', verbose_name='图标名')
     isCustomSvg = models.BooleanField(blank=True, default=False, verbose_name=u'是否是自定义svg图标')
     noKeepAlive = models.BooleanField(blank=True, default=False, verbose_name=u'当前路由是否不缓存')
     noClosable = models.BooleanField(blank=True, default=False, verbose_name=u'当前路由是否可关闭多标签页')
@@ -99,7 +102,7 @@ class MenuModel(models.Model):
     dynamicNewTab = models.BooleanField(blank=True, default=False, verbose_name=u'动态传参路由是否新开标签页')
     redirect = models.CharField(max_length=50, default='', verbose_name='重定向')
     menu_code = models.CharField(max_length=100, default='', verbose_name='菜单编码')
-    menutype  = models.SmallIntegerField(default=1, verbose_name=u'类型') #类型: 1-菜单 2-按钮
+    menutype = models.SmallIntegerField(default=1, verbose_name=u'类型')  # 类型: 1-菜单 2-按钮
     sort = models.IntegerField(default=0, verbose_name='排序')
 
     class Meta:
@@ -107,6 +110,7 @@ class MenuModel(models.Model):
         verbose_name = u'菜单表'
         verbose_name_plural = verbose_name
 
+
 class Role(models.Model):
     rid = models.SmallIntegerField(primary_key=True, unique=True, verbose_name=u'用户角色组ID')
     roleName = models.CharField(max_length=32, unique=True,
@@ -148,6 +152,7 @@ class Role(models.Model):
             permslist.sort()
             return permslist
 
+
 class Device_User(AbstractBaseUser):
     userID = models.CharField(blank=True, max_length=32, primary_key=True,
                               verbose_name=u'用户ID', unique=True)
@@ -175,10 +180,10 @@ class Device_User(AbstractBaseUser):
     language = models.CharField(blank=True, max_length=16, default='en', verbose_name=u'语言地区')
     # 手机注册增加字段
     phone = models.CharField(max_length=16, db_index=True, verbose_name=u'手机号', default='', blank=True)
-    fingerprint_enable = models.SmallIntegerField(default=0, verbose_name=u'是否开启了指纹登录') # 0:未开启,1:已开启
+    fingerprint_enable = models.SmallIntegerField(default=0, verbose_name=u'是否开启了指纹登录')  # 0:未开启,1:已开启
     fingerprint_key = models.CharField(max_length=128, default='', verbose_name=u'客户端用于解码的密钥等信息')
-    is_local = models.BooleanField(blank=True, default=False, verbose_name=u'是否是本地登录用户') # False:账号登录,1:本地登录
-    subscribe_email = models.SmallIntegerField(default=0, verbose_name=u'是否订阅营销邮件') # 0:未订阅,1:订阅,2:不订阅
+    is_local = models.BooleanField(blank=True, default=False, verbose_name=u'是否是本地登录用户')  # False:账号登录,1:本地登录
+    subscribe_email = models.SmallIntegerField(default=0, verbose_name=u'是否订阅营销邮件')  # 0:未订阅,1:订阅,2:不订阅
     region_country = models.IntegerField(blank=True, default=0, verbose_name='地区表唯一标识')
     objects = UserManager()
 
@@ -270,7 +275,7 @@ class Device_Info(models.Model):
     iSNotification = models.BooleanField(blank=True, verbose_name=u'报警通知 0:关闭,1:开启)', default=False)
     isVod = models.SmallIntegerField(blank=True, default=0, verbose_name='是否支持云存')  # 是否支持云存设备
     isExist = models.SmallIntegerField(blank=True, default=1, verbose_name='是否被删除')  # 是否被删除了(需主用户交互) 1存在,0不存在,2设备被重置
-    isCameraOpenCloud =  models.SmallIntegerField(blank=True, default=1, verbose_name='是否开启云存')  # 0:不开启  1:开启
+    isCameraOpenCloud = models.SmallIntegerField(blank=True, default=1, verbose_name='是否开启云存')  # 0:不开启  1:开启
     serial_number = models.CharField(blank=True, max_length=9, default='', verbose_name='关联序列号')
     ###
     REQUIRED_FIELDS = []
@@ -324,7 +329,7 @@ class Equipment_Info(models.Model):
     alarm = models.CharField(blank=True, max_length=256, verbose_name=u'报警信息')
     eventTime = models.CharField(blank=True, db_index=True, default='', max_length=16, verbose_name=u'设备报警时间')
     receiveTime = models.CharField(blank=True, default='', max_length=16, verbose_name=u'接收到报警时间')
-    userID_id = models.CharField(default='',  db_index=True, blank=True, max_length=32, verbose_name=u'用户ID')
+    userID_id = models.CharField(default='', db_index=True, blank=True, max_length=32, verbose_name=u'用户ID')
     is_st = models.SmallIntegerField(default=0, verbose_name='是否截图')  # 0 否,1 是图,2,视频
     storage_location = models.SmallIntegerField(default=1, verbose_name='数据信息存储位置。1:阿里云oss,2:aws')
     borderCoords = models.TextField(default='', blank=True, verbose_name=u'ai类型图片边框位置信息')
@@ -568,7 +573,7 @@ class Ai_Push_Info(models.Model):
     alarm = models.CharField(blank=True, max_length=256, verbose_name=u'报警信息')
     eventTime = models.CharField(blank=True, default='', max_length=16, verbose_name=u'设备报警时间')
     receiveTime = models.CharField(blank=True, default='', max_length=16, verbose_name=u'接收到报警时间')
-    userID_id = models.CharField(default='',  db_index=True, blank=True, max_length=32, verbose_name=u'用户ID')
+    userID_id = models.CharField(default='', db_index=True, blank=True, max_length=32, verbose_name=u'用户ID')
     is_st = models.SmallIntegerField(default=0, verbose_name='是否截图')  # 0 否,1 是图,2,视频
     storage_location = models.SmallIntegerField(default=1, db_index=True, verbose_name='数据信息存储位置。1:阿里云oss,2:aws')
     # message_id = models.CharField(blank=True, max_length=32, default='', verbose_name='第三方推送服务器返回的id')
@@ -700,10 +705,10 @@ class Store_Meal(models.Model):
     symbol = models.CharField(blank=True, default='$', max_length=32, verbose_name=u'符号')
     price = models.CharField(blank=True, max_length=32, verbose_name=u'价格')
     virtual_price = models.CharField(blank=True, max_length=32, verbose_name=u'虚拟价格')
-    is_discounts = models.SmallIntegerField(default=0, verbose_name=u'该套餐是否有优惠 [0=否,1是]')   # 0没有;1有
+    is_discounts = models.SmallIntegerField(default=0, verbose_name=u'该套餐是否有优惠 [0=否,1是]')  # 0没有;1有
     discount_price = models.CharField(blank=True, max_length=32, verbose_name=u'第二年优惠价格')
-    day = models.IntegerField(default=0, blank=True, verbose_name=u'云存录像保存天数(循环)')   # 7,30,180,360
-    expire = models.IntegerField(default=0, blank=True, verbose_name=u'有效期') #单位月
+    day = models.IntegerField(default=0, blank=True, verbose_name=u'云存录像保存天数(循环)')  # 7,30,180,360
+    expire = models.IntegerField(default=0, blank=True, verbose_name=u'有效期')  # 单位月
     # content = models.TextField(blank=True, null=True, verbose_name=u'描述')
     add_time = models.DateTimeField(blank=True, null=True, verbose_name=u'加入时间', auto_now_add=True)
     # type = models.SmallIntegerField(default=0, verbose_name='付款类型')  # 0是paypal,1为支付宝
@@ -717,7 +722,7 @@ class Store_Meal(models.Model):
     # lang = models.CharField(default='', max_length=20, verbose_name='语言/国家')
     lang = models.ManyToManyField(to='Lang', verbose_name='套餐语言', db_table='store_meal_lang')
     cycle_config_id = models.IntegerField(null=True, verbose_name='周期付款配置表id')
-    sort = models.IntegerField(default=99, blank=True, verbose_name=u'排序,越小越靠前') #单位月
+    sort = models.IntegerField(default=99, blank=True, verbose_name=u'排序,越小越靠前')  # 单位月
     # 备用字段
     spare_3 = models.CharField(default='', blank=True, max_length=64, db_index=True, verbose_name=u'备用字段3')
     spare_4 = models.CharField(default='', blank=True, max_length=64, db_index=True, verbose_name=u'备用字段4')
@@ -731,6 +736,7 @@ class Store_Meal(models.Model):
         verbose_name_plural = verbose_name
         ordering = ('id',)
 
+
 class AiStoreMeal(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增ID')
     price = models.CharField(blank=True, max_length=32, verbose_name=u'价格')
@@ -738,9 +744,9 @@ class AiStoreMeal(models.Model):
     symbol = models.CharField(blank=True, default='$', max_length=32, verbose_name=u'符号')
     currency = models.CharField(blank=True, default='$', max_length=32, verbose_name=u'货币符号')
     is_show = models.SmallIntegerField(default=0, verbose_name=u'是否显示')  # 0: 否, 1: 是
-    is_discounts = models.SmallIntegerField(default=0, verbose_name=u'是否有优惠')   # 0: 没有, 1: 有
+    is_discounts = models.SmallIntegerField(default=0, verbose_name=u'是否有优惠')  # 0: 没有, 1: 有
     discount_price = models.CharField(blank=True, max_length=32, verbose_name=u'第二年优惠价格')
-    effective_day = models.IntegerField(default=0, blank=True, verbose_name=u'有效天数')   # 7, 30
+    effective_day = models.IntegerField(default=0, blank=True, verbose_name=u'有效天数')  # 7, 30
     pay_type = models.ManyToManyField(to='Pay_Type', verbose_name='付款类型', db_table='ai_store_meal_pay')
     lang = models.ManyToManyField(to='Lang', verbose_name='套餐语言', db_table='ai_store_meal_lang')
     add_time = models.DateTimeField(blank=True, verbose_name=u'添加时间', auto_now_add=True)
@@ -868,7 +874,7 @@ class Order_Model(models.Model):
     orderID = models.CharField(blank=True, max_length=20, primary_key=True, verbose_name=u'订单id')
     paymentID = models.CharField(blank=True, max_length=64, default='', verbose_name='付款id')
     trade_no = models.CharField(blank=True, max_length=32, default='', verbose_name='第三方订单号')
-    userID = models.ForeignKey(Device_User, to_field='userID', on_delete=models.CASCADE)    # 订单关联用户
+    userID = models.ForeignKey(Device_User, to_field='userID', on_delete=models.CASCADE)  # 订单关联用户
     UID = models.CharField(max_length=20, verbose_name='设备UID')
     channel = models.SmallIntegerField(default=0, verbose_name=u'通道数')
     desc = models.CharField(max_length=50, default='', verbose_name='商品描述')
@@ -884,8 +890,10 @@ class Order_Model(models.Model):
     payType = models.SmallIntegerField(default=0, verbose_name='支付方式')
     payTime = models.IntegerField(verbose_name='支付成功时间', default=0)
     rank = models.ForeignKey(Store_Meal, to_field='id', default='', on_delete=models.CASCADE, verbose_name='关联云存套餐表')
-    ai_rank = models.ForeignKey(AiStoreMeal, to_field='id', default='', on_delete=models.CASCADE, verbose_name='关联ai套餐表')
-    order_type = models.SmallIntegerField(default=0, verbose_name='订单类型:0:云存:1:ai')
+    ai_rank = models.ForeignKey(AiStoreMeal, to_field='id', default='', on_delete=models.CASCADE,
+                                verbose_name='关联ai套餐表')
+    order_type = models.SmallIntegerField(default=0, verbose_name='订单类型:0:云存,1:ai,2:联通4G')
+    unify_combo_id = models.CharField(blank=True, default='', max_length=32, verbose_name=u'统一套餐id')
     nickname = models.CharField(default='', max_length=64, verbose_name='设备昵称')
     uid_bucket_id = models.IntegerField(default=0, verbose_name='关联uid_bucket的字段')
     commodity_type = models.SmallIntegerField(default=0, verbose_name='云存储套餐类型')
@@ -909,16 +917,53 @@ class Order_Model(models.Model):
         verbose_name_plural = verbose_name
         ordering = ('-orderID',)
 
-class CouponModel(models.Model):
+
+# 优惠券文案语言表
+class CouponLang(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name='自增ID')
+    lang = models.CharField(default='', max_length=20, db_index=True, verbose_name='语言/国家')
+    instruction = models.CharField(blank=True, default='', max_length=200, verbose_name=u'用法')
+    quota = models.CharField(blank=True, default='', max_length=200, verbose_name=u'优惠额度, 例如20.0%')
+    unit = models.CharField(blank=True, default='', max_length=200, verbose_name=u'优惠单位, 例如off, 折')
+    remark = models.CharField(blank=True, default='', max_length=200, verbose_name=u'备注')
+
+    def __str__(self):
+        return self.id
+
+    class Meta:
+        db_table = 'coupon_lang'
+        verbose_name = '套餐语言'
+        verbose_name_plural = verbose_name
+        ordering = ('id',)
+
+
+class CouponConfigModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name='主键')
-    userID_id = models.CharField(default='', db_index=True, blank=True, max_length=32, verbose_name=u'用户ID')
-    use_status = models.SmallIntegerField(default=0, verbose_name='使用状态') #0未使用;1冻结;2已使用
-    type = models.SmallIntegerField(default=0, verbose_name='优惠类型') #1打折; 2抵扣
+    type = models.SmallIntegerField(default=0, verbose_name='优惠类型')  # 1打折; 2抵扣
+    use_range = models.CharField(default=0, max_length=100, verbose_name='使用限制')  # 0:所有服务都可使用; 1:云存;2:ai;多种1,2,3
     coupon_discount = models.CharField(blank=True, max_length=32, verbose_name=u'折扣力度')
-    distributeTime = models.IntegerField(verbose_name='发放到用户账户时间', default=0)
-    valid_time = models.PositiveIntegerField(default=9999999999, verbose_name='有效期间')  #超过有效期不可在使用;0永久
-    # use_limit = models.CharField(default=0, max_length=100, verbose_name='使用限制')  #0:所有服务都可使用; 1:云存;2:ai;
-    addTime = models.IntegerField(verbose_name='添加时间', default=0)
+    lang = models.ManyToManyField(to='CouponLang', verbose_name='套餐语言', db_table='coupon_lang_associated')
+
+    def __str__(self):
+        return self.id
+
+    class Meta:
+        db_table = 'coupon_config'
+        verbose_name = u'优惠券配置表'
+        verbose_name_plural = verbose_name
+
+
+class CouponModel(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name='主键')
+    use_status = models.SmallIntegerField(default=0, verbose_name='使用状态')  # 0未使用;1冻结;2已使用
+    distribute_time = models.IntegerField(verbose_name='发放到用户账户时间', default=0)
+    valid_time = models.PositiveIntegerField(default=9999999999, verbose_name='有效期间')  # 超过有效期不可在使用;0永久
+    # userID = models.ForeignKey(Device_User, to_field='userID', on_delete=models.CASCADE)  # 关联用户
+    userID = models.CharField(blank=True, max_length=32, db_index=True, verbose_name=u'用户ID')
+    coupon_config = models.ForeignKey(CouponConfigModel, null=True, blank=True, to_field='id', on_delete=models.CASCADE,
+                                      verbose_name='关联优惠券配置表的id')
+    update_time = models.IntegerField(verbose_name='更新时间', default=0)
+    create_time = models.IntegerField(verbose_name='添加时间', default=0)
 
     def __str__(self):
         return self.id
@@ -928,12 +973,13 @@ class CouponModel(models.Model):
         verbose_name = u'优惠券'
         verbose_name_plural = verbose_name
 
+
 class PayCycleConfigModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name='主键')
     # name = models.CharField(default='',max_length=200, verbose_name='计划名字')
     # tax = models.CharField(max_length=2000, default='', verbose_name='税金')
     cycles = models.IntegerField(verbose_name='周期:0代表无限期', default=0)
-    frequency = models.CharField(max_length=50,verbose_name='频率,MONTH,WEEK,YEAR', default=0)
+    frequency = models.CharField(max_length=50, verbose_name='频率,MONTH,WEEK,YEAR', default=0)
     frequencyInterval = models.IntegerField(default=0, verbose_name='频率周期')
     remark = models.CharField(max_length=1000, default='', verbose_name='备注')
 
@@ -945,16 +991,19 @@ class PayCycleConfigModel(models.Model):
         verbose_name = u'周期付款计划'
         verbose_name_plural = verbose_name
 
+
 class PromotionRuleModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name='主键')
-    ruleName = models.TextField(default='', verbose_name='规则名字')                     #json格式, 例: {"cn":"黑色星期五","en":"Black Friday"}
-    ruleDesc = models.TextField(default='', verbose_name='规则描述')                     #json格式,   例:  {"cn":"买一送一","en":"buy one get one free"}
-    ruleConfig = models.CharField(max_length=2000, default='', verbose_name='规则配置')  #json格式, 例: {"buy": 1, "get": 1}
+    ruleName = models.TextField(default='', verbose_name='规则名字')  # json格式, 例: {"cn":"黑色星期五","en":"Black Friday"}
+    ruleDesc = models.TextField(default='',
+                                verbose_name='规则描述')  # json格式,   例:  {"cn":"买一送一","en":"buy one get one free"}
+    ruleConfig = models.CharField(max_length=2000, default='', verbose_name='规则配置')  # json格式, 例: {"buy": 1, "get": 1}
     startTime = models.IntegerField(verbose_name='促销活动开始时间', default=0)
     endTime = models.IntegerField(verbose_name='促销活动结束时间', default=0)
     status = models.SmallIntegerField(default=0, verbose_name='活动状态:0未进行;1进行中')
     remark = models.CharField(max_length=50, default='', verbose_name='备注')
-    popups = models.CharField(max_length=2000, default='', verbose_name='app弹窗消息')   #json格式 ,例: {"cn":"买一送一","en":"buy one get one free"}
+    popups = models.CharField(max_length=2000, default='',
+                              verbose_name='app弹窗消息')  # json格式 ,例: {"cn":"买一送一","en":"buy one get one free"}
 
     def __str__(self):
         return self.id
@@ -966,7 +1015,6 @@ class PromotionRuleModel(models.Model):
         ordering = ('-id',)
 
 
-
 class VodHlsModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name='回放列表主键')
     uid = models.CharField(max_length=20, verbose_name='设备UID', db_index=True)
@@ -976,8 +1024,7 @@ class VodHlsModel(models.Model):
     sec = models.IntegerField(verbose_name='秒数', default=0)
     bucket = models.ForeignKey(VodBucketModel, blank=True, to_field='id', on_delete=models.CASCADE, default=1,
                                verbose_name='存储空间')
-    fg = models.CharField(max_length=20,verbose_name='ts个数,时间描述片段数') # 阿里为时间片段数,亚马逊为一个32bit整型,前28bit代表ts文件的时长
-
+    fg = models.CharField(max_length=20, verbose_name='ts个数,时间描述片段数')  # 阿里为时间片段数,亚马逊为一个32bit整型,前28bit代表ts文件的时长
 
     def __str__(self):
         return self.id
@@ -1016,7 +1063,7 @@ class StsCrdModel(models.Model):
     addTime = models.IntegerField(verbose_name='添加时间', default=0)
     bucket = models.ForeignKey(VodBucketModel, blank=True, to_field='id', on_delete=models.CASCADE, default=1,
                                verbose_name='存储空间')
-    type = models.SmallIntegerField(default=0,verbose_name='sts类型') # 0:阿里云,1:s3
+    type = models.SmallIntegerField(default=0, verbose_name='sts类型')  # 0:阿里云,1:s3
 
     def __str__(self):
         return self.id
@@ -1046,6 +1093,7 @@ class UID_Bucket(models.Model):
         verbose_name_plural = verbose_name
         ordering = ('id',)
 
+
 class Unused_Uid_Meal(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
     uid = models.CharField(max_length=20, verbose_name='设备UID', db_index=True)
@@ -1054,6 +1102,7 @@ class Unused_Uid_Meal(models.Model):
     addTime = models.IntegerField(verbose_name='添加时间', default=0)
     expire = models.IntegerField(verbose_name='存储桶存储时长[月份]', default=0)
     num = models.IntegerField(verbose_name='个数', default=0)
+
     # effect_time = models.BigIntegerField(verbose_name='生效时间', db_index=True, default=0)
     # uid_bucket = models.ForeignKey(UID_Bucket, blank=True, to_field='id', on_delete=models.CASCADE, default=0,
     #                            verbose_name='uid_bucket关联')
@@ -1111,14 +1160,14 @@ class UidSetModel(models.Model):
     version = models.CharField(max_length=32, verbose_name='设备版本', default='')
     p2p_region = models.CharField(max_length=16, verbose_name='设备p2p区域', default='ALL')  # ALL CN EU US
     cloud_vod = models.SmallIntegerField(default=2, verbose_name='云存开关')  # 0,关闭,1开启,2,不支持
-    tz = models.CharField(default='', max_length=16, verbose_name ='设备时区')  # +8
+    tz = models.CharField(default='', max_length=16, verbose_name='设备时区')  # +8
     video_code = models.SmallIntegerField(default=0, verbose_name='编码类型')  # 0:264,1:265
     nickname = models.CharField(default='', max_length=64, verbose_name='设备昵称')
     ip = models.CharField(max_length=20, default='', verbose_name=u'设备ip')
     # 设备重置后第一次启动时间
     is_alexa = models.IntegerField(default=0, verbose_name='是否支持alexa')  # 0为不支持,1为支持,2为开启alexa发现
     detect_group = models.CharField(default='', max_length=32, verbose_name=u'检测类型')
-    pwd = models.CharField(max_length=32, default='', verbose_name=u'设备密码') # 暂时是预留字段
+    pwd = models.CharField(max_length=32, default='', verbose_name=u'设备密码')  # 暂时是预留字段
     resetTime = models.IntegerField(default=0, verbose_name='设备重置时间')
     region_alexa = models.CharField(max_length=8, verbose_name='设备alexa区域', default='ALL')  # ALL CN EU US
     deviceModel = models.CharField(blank=True, max_length=64, default='', verbose_name=u'设备型号')
@@ -1137,6 +1186,9 @@ class UidSetModel(models.Model):
     double_wifi = models.IntegerField(default=0, verbose_name='是否支持双频wifi。0:不支持,1:支持')
     is_ptz = models.IntegerField(default=0, verbose_name='是否支持云台。0:不支持,1:支持')
     is_ai = models.IntegerField(default=2, verbose_name='是否支持ai')  # 0,关闭,1开启,2,不支持
+    is_notification = models.IntegerField(blank=True, default=1, verbose_name='新加-消息提醒开关')  # 0:关闭,1:开启
+    new_detect_interval = models.IntegerField(blank=True, verbose_name='新加-消息提醒间隔', default=60)  # 秒
+
     class Meta:
         db_table = 'uid_set'
         verbose_name = u'设备配置表'
@@ -1380,7 +1432,7 @@ class GrantCodeModel(models.Model):
 class UserAppFrequencyModel(models.Model):
     id = models.AutoField(primary_key=True)
     user = models.ForeignKey(Device_User, to_field='userID', on_delete=models.CASCADE, verbose_name='关联设备用户表')
-    type = models.SmallIntegerField(default=0, verbose_name='使用频率类型') # 1:每天,2:三天,3:一周,4:两周,5:一个月,6:一个月以上
+    type = models.SmallIntegerField(default=0, verbose_name='使用频率类型')  # 1:每天,2:三天,3:一周,4:两周,5:一个月,6:一个月以上
     data_time = models.IntegerField(default=0, verbose_name='数据时间')
     add_time = models.IntegerField(default=0, verbose_name='添加时间')
     update_time = models.IntegerField(default=0, verbose_name='更新时间')
@@ -1415,6 +1467,7 @@ class AppFrequencyYearStatisticsModel(models.Model):
         verbose_name_plural = verbose_name
         db_table = 'app_frequency_year_statistics'
 
+
 # alexa连接数统计表
 class AlexaConnectStatisticsModel(models.Model):
     id = models.AutoField(primary_key=True)
@@ -1445,7 +1498,7 @@ class AppLogModel(models.Model):
     user = models.ForeignKey(Device_User, to_field='userID', on_delete=models.CASCADE, verbose_name='关联设备用户表')
     uid = models.CharField(max_length=20, default='', verbose_name='设备uid')
     average_delay = models.CharField(max_length=32, default='', verbose_name='最高平均延时')
-    status = models.SmallIntegerField(default=0, verbose_name='失败状态')   # 0: 成功,1: 失败
+    status = models.SmallIntegerField(default=0, verbose_name='失败状态')  # 0: 成功,1: 失败
     filename = models.CharField(max_length=120, default='', verbose_name='文件名')
     add_time = models.IntegerField(default=0, verbose_name='日期')
 
@@ -1460,9 +1513,9 @@ class DeviceLogModel(models.Model):
     ip = models.CharField(default='', max_length=32, verbose_name='ip')
     uid = models.CharField(max_length=32, default='', verbose_name='设备uid')
     serial_number = models.CharField(max_length=9, default='', verbose_name='序列号')
-    status = models.SmallIntegerField(default=0, verbose_name='上传状态')   # 0: 成功,1: 失败, 3非文件形式,与error_info相关
+    status = models.SmallIntegerField(default=0, verbose_name='上传状态')  # 0: 成功,1: 失败, 3非文件形式,与error_info相关
     filename = models.CharField(max_length=120, default='', verbose_name='文件名')
-    error_info = models.TextField(blank = True, default = '', verbose_name='设备异常信息')
+    error_info = models.TextField(blank=True, default='', verbose_name='设备异常信息')
     add_time = models.DateTimeField(blank=True, auto_now_add=True, verbose_name=u'添加时间')
 
     class Meta:
@@ -1504,8 +1557,8 @@ class CountryIPModel(models.Model):
 class ZositechHelpModel(models.Model):
     id = models.AutoField(primary_key=True)
     locale = models.CharField(default='', db_index=True, max_length=50, verbose_name='语言')
-    label_names =  models.CharField(default='', db_index=True, max_length=50, verbose_name='标签')
-    origin  =  models.CharField(default='', db_index=True ,max_length=50, verbose_name='来源')
+    label_names = models.CharField(default='', db_index=True, max_length=50, verbose_name='标签')
+    origin = models.CharField(default='', db_index=True, max_length=50, verbose_name='来源')
     # author_id = models.CharField(default='', max_length=50, verbose_name='管理员id')
     # body = models.TextField(default='', blank=True, verbose_name='内容')
     # comments_disabled = models.CharField(default='', max_length=10, verbose_name='comments')
@@ -1532,7 +1585,8 @@ class EquipmentVersionLimitModel(models.Model):
     id = models.AutoField(primary_key=True)
     type = models.SmallIntegerField(default=0, verbose_name='限制类型')
     content = models.TextField(default='', verbose_name='限制内容')
-    equipment_version = models.ForeignKey(Equipment_Version, to_field='eid', on_delete=models.CASCADE, verbose_name='关联设备版本信息id')
+    equipment_version = models.ForeignKey(Equipment_Version, to_field='eid', on_delete=models.CASCADE,
+                                          verbose_name='关联设备版本信息id')
     status = models.SmallIntegerField(default=0, verbose_name='是否启用。0:不启用,1:启用')
     add_time = models.IntegerField(default=0, verbose_name='添加时间')
     update_time = models.IntegerField(default=0, verbose_name='更新时间')
@@ -1545,9 +1599,10 @@ class EquipmentVersionLimitModel(models.Model):
 
 class ExperienceContextModel(models.Model):
     id = models.AutoField(primary_key=True)
-    experience_type = models.SmallIntegerField(default=0,verbose_name='体验类型') # 0: 免费体验套餐, 1: 激活码
+    experience_type = models.SmallIntegerField(default=0, verbose_name='体验类型')  # 0: 免费体验套餐, 1: 激活码
     uid = models.CharField(max_length=20, default='', verbose_name='设备uid')
     do_time = models.IntegerField(default=0, verbose_name='激活时间')
+
     # is_experience = models.SmallIntegerField(default=0, verbose_name=u'是否云存体验用户')  # 0:不是体验用户,1:是体验用户
 
     class Meta:
@@ -1555,13 +1610,26 @@ class ExperienceContextModel(models.Model):
         verbose_name = '设备体验表'
         verbose_name_plural = verbose_name
 
+
+class ExperienceAiModel(models.Model):
+    id = models.AutoField(primary_key=True)
+    experience_type = models.SmallIntegerField(default=0, verbose_name='体验类型')  # 0: 免费体验, 1: 激活码
+    uid = models.CharField(max_length=20, default='', verbose_name='设备uid')
+    do_time = models.IntegerField(default=0, verbose_name='激活时间')
+
+    class Meta:
+        db_table = 'experience_ai'
+        verbose_name = 'ai体验表'
+        verbose_name_plural = verbose_name
+
+
 class CDKcontextModel(models.Model):
     id = models.AutoField(primary_key=True)
-    cdk = models.CharField(max_length=50,unique=True,verbose_name='激活码')
+    cdk = models.CharField(max_length=50, unique=True, verbose_name='激活码')
     create_time = models.IntegerField(default=0, verbose_name='创建时间')
-    valid_time = models.IntegerField(default=0, verbose_name='有效期间')  #超过有效期激活码不可在激活 ,0:永久
-    is_activate = models.SmallIntegerField(default=0, verbose_name='是否已激活') #0 未激活  1 已激活
-    is_down = models.SmallIntegerField(default=0, verbose_name='是否已下载') #0 未下载 1 已下载
+    valid_time = models.IntegerField(default=0, verbose_name='有效期间')  # 超过有效期激活码不可在激活 ,0:永久
+    is_activate = models.SmallIntegerField(default=0, verbose_name='是否已激活')  # 0 未激活  1 已激活
+    is_down = models.SmallIntegerField(default=0, verbose_name='是否已下载')  # 0 未下载 1 已下载
     rank = models.ForeignKey(Store_Meal, to_field='id', default='', on_delete=models.CASCADE, verbose_name='套餐类型')
     # order = models.ForeignKey(Order_Model, blank=True, max_length=20, to_field='orderID', on_delete=models.CASCADE, verbose_name='订单id', unique=True)
     order = models.CharField(max_length=20, blank=True, unique=True, verbose_name='订单id')
@@ -1612,6 +1680,45 @@ class DeviceTypeModel(models.Model):
         verbose_name_plural = verbose_name
 
 
+class AppBundle(models.Model):
+    id = models.AutoField(primary_key=True)
+    app_bundle_id = models.CharField(default='', max_length=32, verbose_name='app版本id')
+    app_device_type = models.ManyToManyField(to='AppDeviceType', db_table='app_bundle_device_type',
+                                             verbose_name='APP版本-设备类型')
+
+    class Meta:
+        db_table = 'app_bundle'
+        verbose_name = 'APP版本表'
+        verbose_name_plural = verbose_name
+
+
+class AppDeviceType(models.Model):
+    id = models.AutoField(primary_key=True)
+    model = models.SmallIntegerField(default=0, verbose_name='设备类型')  # 1:DVR, 2:IPC
+    type = models.IntegerField(default=0, verbose_name='设备型号')
+    icon = models.CharField(default='', max_length=200, verbose_name='图标文件路径')
+
+    class Meta:
+        db_table = 'app_device_type'
+        verbose_name = 'APP设备类型表'
+        verbose_name_plural = verbose_name
+
+
+class DeviceNameLanguage(models.Model):
+    id = models.AutoField(primary_key=True)
+    app_device_type = models.ForeignKey(AppDeviceType, to_field='id', default='', on_delete=models.CASCADE,
+                                        verbose_name='关联APP设备类型表id')
+    lang = models.CharField(default='', max_length=10, verbose_name='语言')
+    name = models.CharField(default='', max_length=32, verbose_name='设备名称')
+    sort = models.SmallIntegerField(default=0, verbose_name=u'升序排序')
+
+    class Meta:
+        ordering = ('sort',)
+        db_table = 'device_name_language'
+        verbose_name = '设备名称语言表'
+        verbose_name_plural = verbose_name
+
+
 class OperatingLogsModel(models.Model):
     id = models.AutoField(primary_key=True)
     userId = models.CharField(blank=False, max_length=32, db_index=True, verbose_name=u'操作人')
@@ -1640,6 +1747,7 @@ class ProcessInfoLogsModel(models.Model):
         verbose_name = '过程信息表'
         verbose_name_plural = verbose_name
 
+
 class EquipmentLogModel(models.Model):
     id = models.AutoField(primary_key=True)
     user = models.CharField(blank=False, max_length=32, db_index=True, verbose_name=u'操作用户')
@@ -1650,6 +1758,7 @@ class EquipmentLogModel(models.Model):
     time = models.DateTimeField(null=True, blank=True, db_index=True, verbose_name=u'访问时间')
     operatingcontent = models.TextField(blank=True, default='', verbose_name=u'操作内容')
     url = models.CharField(max_length=150, default='', blank=True, verbose_name=u'访问路径')
+
     class Meta:
         db_table = 'equipment_log'
         verbose_name = '设备日志表'
@@ -1693,7 +1802,6 @@ class RegionModel(models.Model):
     continent_code = models.CharField(max_length=3, default='', verbose_name='洲代码')
     api = models.CharField(max_length=50, default='', verbose_name='请求地址')
 
-
     class Meta:
         db_table = 'tb_region'
         verbose_name = '区域表'
@@ -1720,10 +1828,10 @@ class UIDModel(models.Model):
     uid = models.CharField(max_length=20, null=False, db_index=True, unique=True, verbose_name='设备id')
     mac = models.CharField(max_length=17, null=True, default='', verbose_name='设备id对应的mac地址')
     uid_extra = models.TextField(default='', verbose_name='uid的额外描述')
-    status = models.SmallIntegerField(default=0, verbose_name='使用状态')   # 0:未使用, 2:已使用
+    status = models.SmallIntegerField(default=0, verbose_name='使用状态')  # 0:未使用, 2:已使用
     add_time = models.IntegerField(default=0, verbose_name='添加时间')
     update_time = models.IntegerField(default=0, verbose_name='更新时间')
-    area = models.SmallIntegerField(default=0, verbose_name='区域')   # 0:国内, 1:国外
+    area = models.SmallIntegerField(default=0, verbose_name='区域')  # 0:国内, 1:国外
     vpg = models.ForeignKey(VPGModel, to_field='id', default=1, on_delete=models.DO_NOTHING, verbose_name='关联VPG表的id')
     p2p_type = models.IntegerField(default=1, verbose_name='p2p类型。1:宸云,2:tutk')
     full_uid_code = models.CharField(max_length=256, default='', verbose_name='宸云完整uid')
@@ -1895,7 +2003,8 @@ class CompanySerialModel(models.Model):
 class UIDCompanySerialModel(models.Model):
     id = models.AutoField(primary_key=True)
     uid = models.ForeignKey(UIDModel, to_field='id', on_delete=models.CASCADE, verbose_name='关联UID表')
-    company_serial = models.ForeignKey(CompanySerialModel, to_field='id', on_delete=models.CASCADE, verbose_name='6位数序列号')
+    company_serial = models.ForeignKey(CompanySerialModel, to_field='id', on_delete=models.CASCADE,
+                                       verbose_name='6位数序列号')
     add_time = models.IntegerField(default=0, verbose_name='添加时间')
     update_time = models.IntegerField(default=0, verbose_name='更新时间')
 
@@ -1907,7 +2016,8 @@ class UIDCompanySerialModel(models.Model):
 
 class iotdeviceInfoModel(models.Model):
     id = models.AutoField(primary_key=True)
-    serial_number = models.CharField(max_length=11, blank=True, default='', db_index=True, verbose_name=u'关联Device_Info表的序列号')
+    serial_number = models.CharField(max_length=11, blank=True, default='', db_index=True,
+                                     verbose_name=u'关联Device_Info表的序列号')
     uid = models.CharField(blank=True, max_length=32, default='', db_index=True, verbose_name=u'设备UID')
     certificate_id = models.CharField(blank=True, max_length=256, default='', verbose_name=u'证书id')
     certificate_pem = models.TextField(blank=True, default='', verbose_name=u'证书项目')
@@ -1916,9 +2026,10 @@ class iotdeviceInfoModel(models.Model):
     thing_name = models.CharField(blank=True, max_length=256, default='', verbose_name=u'IoT Thing Name')
     thing_groups = models.CharField(blank=True, max_length=256, default='', verbose_name=u'IoT Thing Groups')
     endpoint = models.CharField(blank=True, max_length=256, db_index=True, default='', verbose_name=u'iot端点')
-    token_iot_number = models.CharField(blank=True,  db_index=True, default='', max_length=50, verbose_name='连接iot令牌')
+    token_iot_number = models.CharField(blank=True, db_index=True, default='', max_length=50, verbose_name='连接iot令牌')
     add_time = models.DateTimeField(blank=True, auto_now_add=True, verbose_name=u'添加时间')
     update_time = models.DateTimeField(blank=True, auto_now=True, verbose_name=u'更新时间')
+
     class Meta:
         db_table = 'iot_deviceInfo'
         verbose_name = 'iot设备信息表'
@@ -1930,6 +2041,7 @@ class UIDMainUser(models.Model):
     id = models.AutoField(primary_key=True)
     UID = models.CharField(blank=True, max_length=32, verbose_name=u'设备UID', default='')
     user_id = models.CharField(blank=True, max_length=32, verbose_name=u'用户ID', default='')
+
     class Meta:
         db_table = 'uid_mainuser'
         verbose_name = '设备主用户表'
@@ -1954,7 +2066,6 @@ class Pc_Info(models.Model):
     content = models.TextField(blank=True, default='', verbose_name=u'内容信息')
     authority = models.SmallIntegerField(blank=True, default=0, verbose_name='权限')
 
-
     class Meta:
         db_table = 'pc_info'
         verbose_name = u'pc信息表'
@@ -1971,6 +2082,7 @@ class CloudLogModel(models.Model):
     url = models.CharField(max_length=150, default='', blank=True, verbose_name=u'访问路径')
     time = models.IntegerField(null=True, blank=True, db_index=True, verbose_name=u'访问时间')
     content = models.TextField(blank=True, default='', verbose_name=u'参数内容')
+
     class Meta:
         db_table = 'cloud_log'
         verbose_name = '云存api记录表'
@@ -1995,6 +2107,7 @@ class PctestjobModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
     jobname = models.CharField(blank=True, max_length=32, verbose_name=u'岗位名字')
     jobcode = models.SmallIntegerField(default=3, verbose_name='岗位code 。1:管理员,3:普通人员')
+
     class Meta:
         db_table = 'pctest_job'
         verbose_name = u'pc岗位表'
@@ -2030,6 +2143,7 @@ class PctestfunctionModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
     functionname = models.CharField(blank=True, max_length=32, verbose_name=u'职能名字')
     functioncode = models.SmallIntegerField(default=1, verbose_name='职能code 。PC端自己逻辑判断')
+
     class Meta:
         db_table = 'pctest_function'
         verbose_name = u'pc设备职能表'
@@ -2078,7 +2192,8 @@ class PctestjobdeviceModel(models.Model):
 class PctestModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
     device = models.ForeignKey(PctestdeviceModel, to_field='id', on_delete=models.DO_NOTHING, verbose_name='关联pc测试设备表')
-    function = models.ForeignKey(PctestfunctionModel, to_field='id', on_delete=models.DO_NOTHING, verbose_name='关联pc设备职能表')
+    function = models.ForeignKey(PctestfunctionModel, to_field='id', on_delete=models.DO_NOTHING,
+                                 verbose_name='关联pc设备职能表')
 
     class Meta:
         db_table = 'pctest_device_function'
@@ -2108,12 +2223,9 @@ class P2PIpModel(models.Model):
 
 class DeviceDomainModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
-    uid = models.CharField(max_length=20, default='', unique=True, db_index=True, verbose_name='uid')
-    serial_number = models.CharField(max_length=9, default='', unique=True, db_index=True, verbose_name='序列号')
     ip = models.CharField(default='', max_length=32, verbose_name='ip')
     country_name = models.CharField(max_length=20, default='', verbose_name='国家名')
     api = models.CharField(max_length=50, default='', verbose_name='使用的域名')
-    region_id = models.SmallIntegerField(default=0, verbose_name='地区id')
     add_time = models.DateTimeField(blank=True, auto_now_add=True, verbose_name=u'添加时间')
     update_time = models.DateTimeField(blank=True, auto_now=True, verbose_name=u'更新时间')
 
@@ -2123,6 +2235,22 @@ class DeviceDomainModel(models.Model):
         verbose_name_plural = verbose_name
 
 
+class DeviceDomainRegionModel(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    serial_number = models.CharField(max_length=9, default='', verbose_name='序列号')
+    ip = models.CharField(default='', max_length=32, verbose_name='ip')
+    country_name = models.CharField(max_length=20, default='', verbose_name='国家名')
+    api = models.CharField(max_length=50, default='', verbose_name='使用的域名')
+    region_id = models.SmallIntegerField(default=0, verbose_name='地区id')
+    add_time = models.DateTimeField(blank=True, auto_now_add=True, verbose_name=u'添加时间')
+    update_time = models.DateTimeField(blank=True, auto_now=True, verbose_name=u'更新时间')
+
+    class Meta:
+        db_table = 'device_domain_region'
+        verbose_name = u'设备域名地区表'
+        verbose_name_plural = verbose_name
+
+
 class RequestRecordModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增ID')
     method = models.CharField(max_length=10, default='', verbose_name='请求类型')
@@ -2144,16 +2272,17 @@ class AiService(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
     uid = models.CharField(max_length=20, verbose_name='设备UID', db_index=True)
     channel = models.SmallIntegerField(default=0, verbose_name='通道')
-    orders = models.ForeignKey(Order_Model, to_field='orderID', default='', on_delete=models.CASCADE, verbose_name='关联订单表')
-    detect_status = models.SmallIntegerField(default=0, verbose_name='状态[0:关闭,1:开启]')
+    orders = models.ForeignKey(Order_Model, to_field='orderID', default='', on_delete=models.CASCADE,
+                               verbose_name='关联订单表')
+    detect_status = models.SmallIntegerField(default=0, verbose_name='状态')  # 0:关闭, 1:开启
     endTime = models.BigIntegerField(verbose_name='套餐结束时间', db_index=True, default=0)
     addTime = models.IntegerField(verbose_name='添加时间', default=0)
     updTime = models.BigIntegerField(verbose_name='更新时间', default=0)
-    use_status = models.IntegerField(verbose_name='使用状态[0:未使用,1:使用中,2已过期]', default=0)
-    detect_group = models.CharField(blank=True, default='1', max_length=100, verbose_name='侦测类型[1=人;2=动物;3=车]')
+    use_status = models.IntegerField(verbose_name='使用状态', default=0)  # 0:未使用, 1:使用中, 2:已过期
+    # 1:人, 2:动物, 3:车, 4:包裹
+    detect_group = models.CharField(blank=True, default='1', max_length=100, verbose_name='侦测类型')
     detect_interval = models.IntegerField(verbose_name='推送间隔', default=60)  # 秒
 
-
     class Meta:
         db_table = 'ai_service'
         verbose_name = 'ai设备服务表'
@@ -2263,6 +2392,7 @@ class CloudVodSurveysOperateLog(models.Model):
         verbose_name_plural = verbose_name
         ordering = ('id',)
 
+
 class DeviceOTAUpgradeRecord(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
     created_time = models.IntegerField(default=0, verbose_name='创建时间')
@@ -2272,12 +2402,14 @@ class DeviceOTAUpgradeRecord(models.Model):
     serial_number = models.CharField(max_length=11, blank=True, verbose_name='序列号', default='')
     uid = models.CharField(max_length=22, blank=True, verbose_name='设备UID', default='')
     mci = models.CharField(max_length=10, blank=True, verbose_name='设备大类:IPC/NVR/DVR', default='')
+
     class Meta:
         db_table = 'device_OTA_upgrade_record'
         verbose_name = '设备OTA升级记录'
         verbose_name_plural = verbose_name
         ordering = ('id',)
 
+
 class PaypalWebHookEvent(models.Model):
     id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
     webhook_event_id = models.CharField(max_length=200, blank=True, verbose_name='webhook事件ID', default='')
@@ -2298,4 +2430,272 @@ class PaypalWebHookEvent(models.Model):
         db_table = 'paypal_webhook_event'
         verbose_name = 'paypal钩子事件记录表'
         verbose_name_plural = verbose_name
-        ordering = ('id',)
+        ordering = ('id',)
+
+
+class PopupsConfig(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+
+    title = models.CharField(max_length=1000, verbose_name='弹窗标题', blank=True, default='')
+    content = models.CharField(max_length=1000, verbose_name='弹窗内容', blank=True, default='')
+    tag = models.SmallIntegerField(default=0, verbose_name='app跳转页面标签{1:云存储购 2:AI购买 3:优惠券}')
+    start_time = models.IntegerField(default=0, verbose_name='更新时间')
+    end_time = models.IntegerField(default=0, verbose_name='创建时间')
+    lang = models.CharField(blank=True, max_length=16, default='en', verbose_name=u'语言地区')
+
+    class Meta:
+        db_table = 'popups_config'
+        verbose_name = 'app弹窗设置'
+        verbose_name_plural = verbose_name
+        ordering = ('id',)
+
+
+class RedDotsConfig(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    module = models.CharField(max_length=1000, verbose_name='app模块', blank=True, default='')
+    start_time = models.IntegerField(default=0, verbose_name='更新时间')
+    end_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'red_dots_config'
+        verbose_name = '红点标记配置'
+        verbose_name_plural = verbose_name
+        ordering = ('id',)
+
+
+class UserFamily(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    user = models.ForeignKey(Device_User, to_field='userID', default='', on_delete=models.CASCADE,
+                             verbose_name='关联用户表ID')
+    name = models.CharField(max_length=50, db_index=True, verbose_name=u'家庭名称', default='', blank=True)
+    location = models.CharField(max_length=100, verbose_name='位置', blank=True, default='')
+    sort = models.IntegerField(default=99, blank=True, verbose_name=u'排序,越小越靠前')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'user_family'
+        verbose_name = '用户家庭'
+        verbose_name_plural = verbose_name
+
+
+class FamilyMemberPermission(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    no = models.CharField(max_length=10, unique=True, verbose_name='编号', default=0)
+    name = models.CharField(max_length=50, verbose_name=u'权限名称', default='')
+    sort = models.IntegerField(default=99, blank=True, verbose_name=u'排序,越小越靠前')
+    describe = models.CharField(max_length=128, blank=True, verbose_name='权限描述', default=0)
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'family_member_permission'
+        verbose_name = '家庭成员权限'
+        verbose_name_plural = verbose_name
+
+
+class FamilyMember(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    user = models.ForeignKey(Device_User, to_field='userID', default='', on_delete=models.CASCADE,
+                             verbose_name='关联用户表id')
+    user_name = models.CharField(max_length=64, db_index=True, verbose_name=u'用户名', default='', blank=True)
+    family = models.ForeignKey(UserFamily, to_field='id', default='', on_delete=models.CASCADE,
+                               verbose_name='关联用户家庭id')
+    sort = models.IntegerField(default=99, blank=True, verbose_name=u'排序,越小越靠前')
+    identity = models.SmallIntegerField(default=0, verbose_name='状态{0:普通成员,1:屋主}')
+    permission = models.ForeignKey(FamilyMemberPermission, to_field='id', default='', on_delete=models.CASCADE,
+                                   verbose_name='关联用户家庭id')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'family_member'
+        verbose_name = '家庭成员'
+        verbose_name_plural = verbose_name
+
+
+class FamilyRoom(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    name = models.CharField(max_length=128, verbose_name=u'房间名称', default='', blank=True)
+    family = models.ForeignKey(UserFamily, to_field='id', default='', on_delete=models.CASCADE,
+                               verbose_name='关联用户家庭id')
+    sort = models.IntegerField(default=99, blank=True, verbose_name=u'排序,越小越靠前')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'family_room'
+        verbose_name = '家庭房间'
+        verbose_name_plural = verbose_name
+
+
+class GatewaySubDevice(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    device = models.ForeignKey(Device_Info, to_field='id', default='', on_delete=models.CASCADE,
+                               verbose_name='关联设备信息id')
+    device_type = models.SmallIntegerField(default=0, verbose_name=u'设备类型')
+    nickname = models.CharField(default='', max_length=32, verbose_name=u'设备名称')
+    ieee_addr = models.CharField(default='', max_length=64, verbose_name=u'长地址')
+    src_addr = models.CharField(default='', max_length=16, verbose_name=u'短地址')
+    status = models.SmallIntegerField(default=0, verbose_name='状态')  # 0:关闭, 1:开启
+    is_tampered = models.SmallIntegerField(default=0, verbose_name='拆动状态')  # 0:正常, 1:被拆动
+    mac = models.CharField(default='', max_length=32, verbose_name=u'mac地址')
+    device_model = models.CharField(default='', max_length=16, verbose_name=u'设备型号')
+    manufacturer = models.CharField(default='', max_length=16, verbose_name=u'制造商')
+    sensor_serial = models.CharField(default='', max_length=32, verbose_name=u'传感器序列号')
+    firmware_version = models.CharField(default='', max_length=32, verbose_name=u'固件版本')
+    hardware_version = models.CharField(default='', max_length=32, verbose_name=u'硬件版本')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+
+    class Meta:
+        db_table = 'gateway_sub_device'
+        verbose_name = '网关子设备'
+        verbose_name_plural = verbose_name
+
+
+class FamilyRoomDevice(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    room_id = models.IntegerField(default=0, blank=True, verbose_name=u'房间id')
+    family_id = models.IntegerField(default=0, blank=True, verbose_name=u'家庭id')
+    device = models.ForeignKey(Device_Info, to_field='id', default='', on_delete=models.CASCADE,
+                               verbose_name='关联设备信息id')
+    sub_device = models.IntegerField(default=0, blank=True, verbose_name='关联子设备信息id')
+    sort = models.IntegerField(default=0, blank=True, verbose_name=u'排序,越小越靠前')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'family_room_device'
+        verbose_name = '家庭房间关联设备'
+        verbose_name_plural = verbose_name
+
+
+class FamilyMemberJoin(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    user = models.ForeignKey(Device_User, to_field='userID', default='', on_delete=models.CASCADE,
+                             verbose_name='关联用户表id')
+    family = models.ForeignKey(UserFamily, to_field='id', default='', on_delete=models.CASCADE,
+                               verbose_name='关联用户家庭id')
+    status = models.SmallIntegerField(default=0, verbose_name='状态{0:未确认,1:拒绝,2:同意}')
+    # 0:家庭所有者搜索邀请,1:成员扫码加入
+    type = models.SmallIntegerField(default=0, verbose_name='类型')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'family_member_join'
+        verbose_name = '家庭成员加入'
+        verbose_name_plural = verbose_name
+
+
+class SensorRecord(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记ID')
+    gateway_sub_device_id = models.IntegerField(default=0, blank=True, verbose_name=u'网关子设备id')
+    alarm = models.CharField(default='', max_length=32, verbose_name=u'消息内容')
+    event_type = models.SmallIntegerField(default=0, verbose_name=u'消息类型')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'sensor_record'
+        verbose_name = '传感器记录'
+        verbose_name_plural = verbose_name
+        app_label = 'PushModel'
+
+
+class UnicomCombo(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记Id')
+    package_id = models.CharField(default='', max_length=32, verbose_name=u'联通套餐id')
+    combo_name = models.CharField(default='', max_length=32, verbose_name=u'套餐名称')
+    status = models.SmallIntegerField(default=0, verbose_name='状态{0:开启,1:停用}')
+    flow_total = models.IntegerField(default=0, blank=True, verbose_name=u'流量总量值 单位(MB)')
+    expiration_days = models.IntegerField(default=0, blank=True, verbose_name=u'有效期天数')
+    expiration_type = models.SmallIntegerField(default=0, verbose_name=u'有效期类型,0=天,1=月,2=年')
+    pay_type = models.ManyToManyField(to='Pay_Type', verbose_name='付款类型', db_table='unicom_combo_pay_type')
+    price = models.CharField(blank=True, max_length=32, verbose_name=u'价格')
+    sort = models.IntegerField(default=0, blank=True, verbose_name=u'排序,越小越靠前')
+    remark = models.TextField(blank=True, default='', verbose_name=u'描述信息')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+    is_show = models.SmallIntegerField(default=1, verbose_name=u'是否显示,0:不显示,1:显示')
+    is_del = models.BooleanField(blank=True, default=False, verbose_name=u'是否删除默认false')
+
+    class Meta:
+        db_table = 'unicom_combo'
+        verbose_name = '联通套餐表'
+        verbose_name_plural = verbose_name
+
+
+class UnicomComboOrderInfo(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记Id')
+    iccid = models.CharField(default='', max_length=32, verbose_name=u'完整的20位纯数字ICCID')
+    renew_list_id = models.CharField(default='', max_length=32, verbose_name=u'联通订购成功套餐队列id')
+    status = models.SmallIntegerField(default=0, verbose_name='状态{0:未使用,1:已使用,2:已过期}')
+    orders = models.ForeignKey(Order_Model, to_field='orderID', default='', on_delete=models.CASCADE,
+                               verbose_name='关联订单表')
+    combo = models.ForeignKey(UnicomCombo, to_field='id', default='', on_delete=models.CASCADE,
+                              verbose_name=u'联通套餐表')
+    activation_time = models.IntegerField(default=0, verbose_name='激活时间')
+    expire_time = models.IntegerField(default=0, verbose_name='过期时间')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+    is_del = models.BooleanField(blank=True, default=False, verbose_name=u'是否删除')
+
+    class Meta:
+        db_table = 'unicom_combo_order_info'
+        verbose_name = '联通套餐订单信息表'
+        verbose_name_plural = verbose_name
+
+
+class UnicomDeviceInfo(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记Id')
+    iccid = models.CharField(default='', max_length=32, verbose_name=u'完整的20位纯数字ICCID')
+    serial_no = models.CharField(default='', max_length=32, verbose_name=u'设备序列号')
+    user_id = models.CharField(blank=True, max_length=32, verbose_name=u'用户id')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'unicom_device_info'
+        verbose_name = '联通设备信息'
+        verbose_name_plural = verbose_name
+
+
+class UnicomDeviceQueueMonitoringPush(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记Id')
+    iccid = models.CharField(default='', max_length=32, verbose_name=u'完整的20位纯数字ICCID')
+    serial_no = models.CharField(default='', max_length=32, verbose_name=u'序列号')
+    sign = models.CharField(default='', max_length=32, verbose_name=u'验证签名')
+    time = models.CharField(default='', max_length=32, verbose_name=u'推送时间(yyyymmddhhmiss)')
+    type = models.CharField(default='', max_length=32, verbose_name=u'推送类型:DEVICE_QUEUE_MONITORING:设队列监控;')
+    current_renew_list_id = models.CharField(default='', max_length=32, verbose_name=u'队列ID')
+    no_queue_available = models.SmallIntegerField(default=0, verbose_name=u'是否无可用队列;1:是;0:否')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'unicom_device_queue_monitoring_push'
+        verbose_name = '联通设备队列监控推送表'
+        verbose_name_plural = verbose_name
+
+
+class UnicomDeviceStatusChangePush(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name=u'自增标记Id')
+    iccid = models.CharField(default='', max_length=32, verbose_name=u'完整的20位纯数字ICCID')
+    serial_no = models.CharField(default='', max_length=32, verbose_name=u'序列号')
+    sign = models.CharField(default='', max_length=32, verbose_name=u'验证签名')
+    time = models.CharField(default='', max_length=32, verbose_name=u'推送时间(yyyymmddhhmiss)')
+    # 变更类型: DEVICE_REAL_NAME_STATUS_CHANGE:实名状态变更;DEVICE_STATUS_CHANGE:设备状态变更;
+    type = models.CharField(default='', max_length=32, verbose_name=u'变更类型')
+    # 之前值:设备状态变更 1: 已激活;2: 可激活;3: 已停用;4: 已失效;5: 可测试;6: 库存;7: 已更换;8: 已清除;
+    previous_val = models.CharField(default='', max_length=32, verbose_name=u'之前值:')
+    # 当前值:设备状态变更 1:已激活;2:可激活;3:已停用;4:已失效;5:可测试;6:库存;7:已更换;8:已清除;
+    current_val = models.CharField(default='', max_length=32, verbose_name=u'当前值:')
+    reason = models.SmallIntegerField(default=0, verbose_name=u'变更原因编码:请查看【信息提示码】')
+    updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    created_time = models.IntegerField(default=0, verbose_name='创建时间')
+
+    class Meta:
+        db_table = 'unicom_device_status_change_push'
+        verbose_name = '联通设备状态变更推送表'
+        verbose_name_plural = verbose_name

+ 8 - 16
Object/IOTCore/IotObject.py

@@ -19,23 +19,15 @@ class IOTClient:
                                        aws_secret_access_key=AWS_IOT_SES_ACCESS_CHINA_SECRET)
             # 终端节点: https://cn-northwest-1.console.amazonaws.cn/iot/home?region=cn-northwest-1#/settings
             self.endpoint = 'a250bbr0p9u7as-ats.iot.cn-northwest-1.amazonaws.com.cn'
-            self.iotrole = AWS_IOT_SES_ACCESS_CHINA_ROLE
+            self.iot_role = AWS_IOT_SES_ACCESS_CHINA_ROLE
 
-        elif region_id == 2:
-            # 亚太新加坡
-            self.client = boto3.client('iot', region_name=AWS_IOT_SES_ACCESS_FOREIGN_REGION_ASIA,
-                                       aws_access_key_id=AWS_IOT_SES_ACCESS_FOREIGN_ID,
-                                       aws_secret_access_key=AWS_IOT_SES_ACCESS_FOREIGN_SECRET)
-            self.endpoint = 'a2rqy12o004ad8-ats.iot.ap-southeast-1.amazonaws.com'
-            self.iotrole = AWS_IOT_SES_ACCESS_FOREIGN_ROLE
-
-        elif region_id == 3:
+        elif region_id == 2 or region_id == 3:
             # 美东弗吉尼亚
             self.client = boto3.client('iot', region_name=AWS_IOT_SES_ACCESS_FOREIGN_REGION_AMERICA,
                                        aws_access_key_id=AWS_IOT_SES_ACCESS_FOREIGN_ID,
                                        aws_secret_access_key=AWS_IOT_SES_ACCESS_FOREIGN_SECRET)
             self.endpoint = 'a2rqy12o004ad8-ats.iot.us-east-1.amazonaws.com'
-            self.iotrole = AWS_IOT_SES_ACCESS_FOREIGN_ROLE
+            self.iot_role = AWS_IOT_SES_ACCESS_FOREIGN_ROLE
 
         elif region_id == 4:
             # 西欧爱尔兰
@@ -43,7 +35,7 @@ class IOTClient:
                                        aws_access_key_id=AWS_IOT_SES_ACCESS_FOREIGN_ID,
                                        aws_secret_access_key=AWS_IOT_SES_ACCESS_FOREIGN_SECRET)
             self.endpoint = 'a2rqy12o004ad8-ats.iot.eu-west-1.amazonaws.com'
-            self.iotrole = AWS_IOT_SES_ACCESS_FOREIGN_ROLE
+            self.iot_role = AWS_IOT_SES_ACCESS_FOREIGN_ROLE
 
     def create_provisioning_claim(self, templateName):
 
@@ -145,17 +137,17 @@ class IOTClient:
                 templateBody=templateBody,
                 parameters=parameters
             )
-            topicsql = "SELECT * FROM 'my/things/" + ThingName + "/shadow/update_lwt'"
+            topic_sql = "SELECT * FROM 'my/things/{}/shadow/update_lwt'".format(ThingName)
             self.client.create_topic_rule(
-                ruleName= ThingName + '_LWT',
+                ruleName=ThingName + '_LWT',
                 topicRulePayload={
-                    "sql": topicsql,
+                    "sql": topic_sql,
                     "ruleDisabled": False,
                     "awsIotSqlVersion": "2016-03-23",
                     'actions': [
                         {
                             'republish': {
-                                'roleArn': self.iotrole,
+                                'roleArn': self.iot_role,
                                 'topic': '$$aws/things/' + ThingName + '/shadow/update',
                                 'qos': 1
                             }

+ 3 - 1
Object/ResponseObject.py

@@ -113,6 +113,7 @@ class ResponseObject(object):
             10054: 'The AI service has expired',
             10055: 'The AI does not recognize any labels',
             10056: 'The device has enabled automatic renewal of cloud storage package and cannot be transferred for the time being',
+            10057: "Can't delete",
         }
         data_cn = {
             0: '成功',
@@ -219,6 +220,7 @@ class ResponseObject(object):
             10054: 'AI服务已过期',
             10055: 'AI没有识别到任何标签',
             10056: '该设备已开通云存套餐自动续费,暂时无法转移',
+            10057: '不能删除',
         }
         if self.lang == 'cn':
             msg = data_cn
@@ -234,7 +236,7 @@ class ResponseObject(object):
             message = '系统错误,code不存在'
         print(self.lang == 'cn')
         print(msg)
-        if self.returntype=='pc':
+        if self.returntype == 'pc':
             return {'code': code, 'msg': message, 'data': res, 'error_code': code}
         return {'result_code': code, 'reason': message, 'result': res, 'error_code': code}
 

+ 256 - 0
Object/UnicomObject.py

@@ -0,0 +1,256 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : UnicomObject.py
+@Time    : 2022/6/17 11:03
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import base64
+import json
+
+import requests
+from Crypto.Cipher import AES
+from decimal import Decimal
+from Object.utils import SM3Util
+
+from Ansjer.config import unicomAppUrl, unicomAppId, unicomAppSecret, unicomTenantId, \
+    unicomEncodeKey, unicomIvKey, unicomUserName, unicomPassword, unicomPushKey
+from Object.utils.SymmetricCryptoUtil import AESencrypt
+
+"""
+联通4Gapi
+具体参数查看接口文档
+https://www.showdoc.com.cn/unicomJYHapi/8158648460007467
+"""
+
+
+class UnicomObjeect:
+    def __init__(self):
+        self.appUrl = unicomAppUrl
+        self.appId = unicomAppId
+        self.appSecret = unicomAppSecret
+        self.tenantId = unicomTenantId
+        self.encodeKey = unicomEncodeKey
+        self.ivKey = unicomIvKey
+        self.username = unicomUserName
+        self.password = unicomPassword
+        self.pushKey = unicomPushKey
+        self.headers = {'Tenant': self.tenantId, 'content-type': 'application/x-www-form-urlencoded'}
+
+    # pip install snowland-smx
+    def createSign(self, reverse=False, **sign_params):
+        """
+        调用接口(API)时需要对请求参数进行签名(sign)验证,
+        算法:
+        根据参数名称将你的所有请求参数按照字母先后顺序排序: key = value & key = value,对除签名外的所有请求参数按
+        key
+        做的升序排列。
+        如:将
+        foo = 1, bar = 2, baz = 3
+        排序为
+        bar = 2, baz = 3, foo = 1
+        参数名和参数值链接后,得到拼装字符串(注意:参数名与参数值左右两边不能包含空格)
+        bar = 2 & baz = 3 & foo = 1
+        将
+        pushkey拼接到参数字符尾部进行
+        SM3
+        加密,再转化成大写,格式是
+        (SM3(key1=value1 & key2=value2 &...& key= pushKey)).upcase
+        @param reverse:
+        @param sign_params:
+        @return:
+        """
+        dict_2 = dict(sorted(sign_params.items(), key=lambda item: item[0], reverse=reverse))
+        data_list = []
+        for item in dict_2.items():
+            if item[0] and item[1]:
+                data_list.append("{}={}".format(item[0], item[1]))
+        val = '&'.join(data_list)
+        push_key = '&key={}'.format(self.pushKey)
+        val = val + push_key
+        return SM3Util.Hash_sm3(val).upper()
+
+    def get_login_authorization(self):
+        """
+        获取登录授权
+        注意登录认证Authorization和登录后的请求认证不一样
+        算法:appId+":"+appSecret base64转码生成 前面加Basic
+        @return: "Basic " + base64_data
+        """
+        voucher = self.appId + ':' + self.appSecret
+        base64_data = str(base64.b64encode(voucher.encode("utf-8")), "utf-8")
+        return "Basic " + base64_data
+
+    def get_encode_password(self):
+        """
+        获取对称加密
+        @return: encrypt_pwd
+        """
+        aes = AESencrypt(self.encodeKey.encode('utf-8'), AES.MODE_CBC, self.ivKey.encode('utf-8'),
+                         paddingMode="ZeroPadding",
+                         characterSet='utf-8')
+        encrypt_pwd = aes.encryptFromString(self.password)
+        return encrypt_pwd
+
+    def generate_token(self):
+        """
+        生成令牌
+        @return: token
+        """
+        url = self.appUrl + '/auc/oauth/token'
+        pwd = self.get_encode_password()
+        body = {'username': self.username, 'password': pwd, 'grant_type': 'password', 'scope': 'server'}
+        headers = self.headers
+        headers['Authorization'] = self.get_login_authorization()
+        response_data = requests.post(url, data=body, headers=headers)
+        response_data = json.loads(response_data.text)
+        return response_data['access_token']
+
+    def refresh_token(self, refresh_token):
+        """
+        刷新令牌
+        @param refresh_token:
+        @return:
+        """
+        url = self.appUrl + '/auc/oauth/token?grant_type=refresh_token'
+        body = {'refresh_token': refresh_token, 'grant_type': 'refresh_token'}
+        headers = self.headers
+        headers['Authorization'] = self.get_login_authorization()
+        response_data = requests.post(url, data=body, headers=headers)
+        return response_data.text
+
+    def business_unify_headers(self):
+        """
+        业务统一headers
+        在请求头中增加key为Authorization,value为"Bearer " + token
+        @return: headers
+        """
+        token = self.generate_token()
+        headers = self.headers
+        headers['Authorization'] = 'Bearer ' + token
+        return headers
+
+    # 业务api  注意 get与post 请求数据类型不同 post使用:application/json
+    def verify_device(self, **re_params):
+        """
+        验证设备
+        @param re_params:
+        @return:
+        """
+        url = self.appUrl + '/platform/api/device/verify-device'
+        return requests.get(url, params=re_params, headers=self.business_unify_headers())
+
+    def query_device_status(self, **re_params):
+        """
+        查询设备状态
+        @param re_params:
+        @return:
+        """
+        url = self.appUrl + '/platform/api/device/device-status'
+        return requests.get(url, params=re_params, headers=self.business_unify_headers())
+
+    def update_device_state(self, **re_data):
+        """
+        修改设备状态
+        @param re_data:
+        @return:
+        """
+        url = self.appUrl + '/platform/api/device/device-state'
+        headers = self.business_unify_headers()
+        headers['content-type'] = 'application/json'
+        return requests.post(url, data=json.dumps(re_data), headers=headers)
+
+    def query_device_usage_history(self, **re_params):
+        """
+        查询设备用量历史
+        @param re_params:
+        @return:
+        """
+        url = self.appUrl + '/platform/api/usage/device-usage-history'
+        return requests.get(url, params=re_params, headers=self.business_unify_headers())
+
+    def query_current_renew_list_usage_details(self, **re_params):
+        """
+        查询设备当前队列用量详情
+        @param re_params:
+        @return:
+        """
+        url = self.appUrl + '/platform/api/usage/current-renewlist-usage-details'
+        return requests.get(url, params=re_params, headers=self.business_unify_headers())
+
+    def get_device_batch_detail(self, **re_data):
+        """
+        查询设备当前队列用量详情
+        @return:
+        """
+        url = self.appUrl + '/platform/api/device/batch-detail'
+        headers = self.business_unify_headers()
+        headers['content-type'] = 'application/json'
+        return requests.post(url, data=json.dumps(re_data), headers=headers)
+
+    def query_package_list(self, **re_params):
+        """
+        查询套餐列表
+        @return:
+        """
+        url = self.appUrl + '/platform/api/package/list'
+        return requests.get(url, params=re_params, headers=self.business_unify_headers())
+
+    def query_renewal_list(self, **re_params):
+        """
+        续费套餐列表
+        @param re_params:
+        @return:
+        """
+        url = self.appUrl + '/platform/api/package/list'
+        return requests.get(url, params=re_params, headers=self.business_unify_headers())
+
+    def async_buy_package(self, **re_data):
+        """
+        查询设备当前队列用量详情
+        @return:
+        """
+        url = self.appUrl + '/platform/api/package/async-buy-package'
+        headers = self.business_unify_headers()
+        headers['content-type'] = 'application/json'
+        return requests.post(url, data=json.dumps(re_data), headers=headers)
+
+
+if __name__ == '__main__':
+    price = '12.13'
+    print(float(price))
+    discount = '6'
+    dd = Decimal(price) - Decimal(discount)
+    print(dd.quantize(Decimal('0.00')))
+
+    unicom_api = UnicomObjeect()
+    data = {'foo': 1, 'bar': 2, 'baz': 3}
+    print(unicom_api.createSign(**data))
+    # result = unicom_api.generate_token()
+    # result = unicom_api.refresh_token('5d0c0f30-99bd-4f17-9614-3524495b05d4')
+    params = {'iccid': '89860620170009628001'}
+    response = unicom_api.verify_device(**params)
+    # response = unicom_api.query_device_status(**params)
+    # response = unicom_api.update_device_state(**params)
+    # response = unicom_api.query_device_usage_history(**params)
+    # response = unicom_api.query_current_renew_list_usage_details(**params)
+    # unicom_api.get_device_batch_detail()
+    # response = unicom_api.query_package_list(**params)
+    # response = unicom_api.query_renewal_list(**params)
+
+    if response.status_code == 200:
+        res_dict = json.loads(response.text)
+        print(res_dict)
+    response_json = {
+        "success": True,
+        "msg": "操作成功",
+        "code": 0,
+        "data": {
+            "iccid": "8986062018007784202",
+            "completeIccid": "89860620180077842020",
+            "status": 1
+        }
+    }
+    print(response_json['data']['iccid'])
+    print(response.status_code)

+ 237 - 0
Object/utils/SM3Util.py

@@ -0,0 +1,237 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : SM3Util.py
+@Time    : 2022/6/28 8:34
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+# -*- encoding: utf-8 -*-
+"""
+@File    : SM32.py
+@Time    : 2022/6/28 9:49
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+from math import ceil
+
+IV = "7380166f 4914b2b9 172442d7 da8a0600 a96f30bc 163138aa e38dee4d b0fb0e4e"
+IV = int(IV.replace(" ", ""), 16)
+a = []
+for i in range(0, 8):
+    a.append(0)
+    a[i] = (IV >> ((7 - i) * 32)) & 0xFFFFFFFF
+IV = a
+
+
+def out_hex(list1):
+    for i in list1:
+        print("%08x" % i)
+    print("\n")
+
+
+def rotate_left(a, k):
+    k = k % 32
+    return ((a << k) & 0xFFFFFFFF) | ((a & 0xFFFFFFFF) >> (32 - k))
+
+
+T_j = []
+for i in range(0, 16):
+    T_j.append(0)
+    T_j[i] = 0x79cc4519
+for i in range(16, 64):
+    T_j.append(0)
+    T_j[i] = 0x7a879d8a
+
+
+def FF_j(X, Y, Z, j):
+    if 0 <= j < 16:
+        ret = X ^ Y ^ Z
+    elif 16 <= j < 64:
+        ret = (X & Y) | (X & Z) | (Y & Z)
+    return ret
+
+
+def GG_j(X, Y, Z, j):
+    if 0 <= j < 16:
+        ret = X ^ Y ^ Z
+    elif 16 <= j < 64:
+        # ret = (X | Y) & ((2 ** 32 - 1 - X) | Z)
+        ret = (X & Y) | ((~ X) & Z)
+    return ret
+
+
+def P_0(X):
+    return X ^ (rotate_left(X, 9)) ^ (rotate_left(X, 17))
+
+
+def P_1(X):
+    return X ^ (rotate_left(X, 15)) ^ (rotate_left(X, 23))
+
+
+def CF(V_i, B_i):
+    W = []
+    for i in range(16):
+        weight = 0x1000000
+        data = 0
+        for k in range(i * 4, (i + 1) * 4):
+            data = data + B_i[k] * weight
+            weight = int(weight / 0x100)
+        W.append(data)
+
+    for j in range(16, 68):
+        W.append(0)
+        W[j] = P_1(W[j - 16] ^ W[j - 9] ^ (rotate_left(W[j - 3], 15))) ^ (rotate_left(W[j - 13], 7)) ^ W[j - 6]
+        str1 = "%08x" % W[j]
+    W_1 = []
+    for j in range(0, 64):
+        W_1.append(0)
+        W_1[j] = W[j] ^ W[j + 4]
+        str1 = "%08x" % W_1[j]
+
+    A, B, C, D, E, F, G, H = V_i
+    """
+    print "00",
+    out_hex([A, B, C, D, E, F, G, H])
+    """
+    for j in range(0, 64):
+        SS1 = rotate_left(((rotate_left(A, 12)) + E + (rotate_left(T_j[j], j))) & 0xFFFFFFFF, 7)
+        SS2 = SS1 ^ (rotate_left(A, 12))
+        TT1 = (FF_j(A, B, C, j) + D + SS2 + W_1[j]) & 0xFFFFFFFF
+        TT2 = (GG_j(E, F, G, j) + H + SS1 + W[j]) & 0xFFFFFFFF
+        D = C
+        C = rotate_left(B, 9)
+        B = A
+        A = TT1
+        H = G
+        G = rotate_left(F, 19)
+        F = E
+        E = P_0(TT2)
+
+        A = A & 0xFFFFFFFF
+        B = B & 0xFFFFFFFF
+        C = C & 0xFFFFFFFF
+        D = D & 0xFFFFFFFF
+        E = E & 0xFFFFFFFF
+        F = F & 0xFFFFFFFF
+        G = G & 0xFFFFFFFF
+        H = H & 0xFFFFFFFF
+        """
+        str1 = "%02d" % j
+        if str1[0] == "0":
+            str1 = ' ' + str1[1:]
+        print str1,
+        out_hex([A, B, C, D, E, F, G, H])
+        """
+
+    V_i_1 = [A ^ V_i[0], B ^ V_i[1], C ^ V_i[2], D ^ V_i[3], E ^ V_i[4], F ^ V_i[5], G ^ V_i[6], H ^ V_i[7]]
+    return V_i_1
+
+
+def hash_msg(msg):
+    # print(msg)
+    len1 = len(msg)
+    reserve1 = len1 % 64
+    msg.append(0x80)
+    reserve1 = reserve1 + 1
+    # 56-64, add 64 byte
+    range_end = 56
+    if reserve1 > range_end:
+        range_end = range_end + 64
+
+    for i in range(reserve1, range_end):
+        msg.append(0x00)
+
+    bit_length = len1 * 8
+    bit_length_str = [bit_length % 0x100]
+    for i in range(7):
+        bit_length = int(bit_length / 0x100)
+        bit_length_str.append(bit_length % 0x100)
+    for i in range(8):
+        msg.append(bit_length_str[7 - i])
+
+    # print(msg)
+
+    group_count = round(len(msg) / 64)
+
+    B = []
+    for i in range(0, group_count):
+        B.append(msg[i * 64:(i + 1) * 64])
+
+    V = [IV]
+    for i in range(0, group_count):
+        V.append(CF(V[i], B[i]))
+
+    y = V[i + 1]
+    result = ""
+    for i in y:
+        result = '%s%08x' % (result, i)
+    return result
+
+
+def str2byte(msg):  # 字符串转换成byte数组
+    ml = len(msg)
+    msg_byte = []
+    msg_bytearray = msg.encode('utf-8')
+    for i in range(ml):
+        msg_byte.append(msg_bytearray[i])
+    return msg_byte
+
+
+def byte2str(msg):  # byte数组转字符串
+    ml = len(msg)
+    str1 = b""
+    for i in range(ml):
+        str1 += b'%c' % msg[i]
+    return str1.decode('utf-8')
+
+
+def hex2byte(msg):  # 16进制字符串转换成byte数组
+    ml = len(msg)
+    if ml % 2 != 0:
+        msg = '0' + msg
+    ml = int(len(msg) / 2)
+    msg_byte = []
+    for i in range(ml):
+        msg_byte.append(int(msg[i * 2:i * 2 + 2], 16))
+    return msg_byte
+
+
+def byte2hex(msg):  # byte数组转换成16进制字符串
+    ml = len(msg)
+    hexstr = ""
+    for i in range(ml):
+        hexstr = hexstr + ('%02x' % msg[i])
+    return hexstr
+
+
+def Hash_sm3(msg, Hexstr=0):
+    if Hexstr:
+        msg_byte = hex2byte(msg)
+    else:
+        msg_byte = str2byte(msg)
+    return hash_msg(msg_byte)
+
+
+def KDF(Z, klen):  # Z为16进制表示的比特串(str),klen为密钥长度(单位byte)
+    klen = int(klen)
+    ct = 0x00000001
+    rcnt = ceil(klen / 32)
+    Zin = hex2byte(Z)
+    Ha = ""
+    for i in range(rcnt):
+        msg = Zin + hex2byte('%08x' % ct)
+        # print(msg)
+        Ha = Ha + hash_msg(msg)
+        # print(Ha)
+        ct += 1
+    return Ha[0: klen * 2]
+
+
+if __name__ == '__main__':
+    y = Hash_sm3('aee694b9e5908ee9878de590afe7949f010000003d2e8b123c2e8b1211180000be3e', 1)
+    print(y)
+
+    # klen = 19
+    # print(KDF("57E7B63623FAE5F08CDA468E872A20AFA03DED41BF1403770E040DC83AF31A67991F2B01EBF9EFD8881F0A0493000603", klen))

+ 216 - 0
Object/utils/SymmetricCryptoUtil.py

@@ -0,0 +1,216 @@
+from Crypto.Cipher import AES
+import base64
+import binascii
+
+
+# 数据类
+class MData:
+    def __init__(self, data=b"", characterSet='utf-8'):
+        # data肯定为bytes
+        self.data = data
+        self.characterSet = characterSet
+
+    def saveData(self, FileName):
+        with open(FileName, 'wb') as f:
+            f.write(self.data)
+
+    def fromString(self, data):
+        self.data = data.encode(self.characterSet)
+        return self.data
+
+    def fromBase64(self, data):
+        self.data = base64.b64decode(data.encode(self.characterSet))
+        return self.data
+
+    def fromHexStr(self, data):
+        self.data = binascii.a2b_hex(data)
+        return self.data
+
+    def toString(self):
+        return self.data.decode(self.characterSet)
+
+    def toBase64(self):
+        return base64.b64encode(self.data).decode()
+
+    def toHexStr(self):
+        return binascii.b2a_hex(self.data).decode()
+
+    def toBytes(self):
+        return self.data
+
+    def __str__(self):
+        try:
+            return self.toString()
+        except Exception:
+            return self.toBase64()
+
+
+"""
+封装类
+"""
+
+
+class AESencrypt:
+    def __init__(self, encode_key, mode, iv_key=b'', paddingMode="NoPadding", characterSet="utf-8"):
+        """
+        构建一个AES对象
+        key: 秘钥,字节型数据
+        mode: 使用模式,只提供两种,AES.MODE_CBC, AES.MODE_ECB
+        iv: iv偏移量,字节型数据
+        paddingMode: 填充模式,默认为NoPadding, 可选NoPadding,ZeroPadding,PKCS5Padding,PKCS7Padding
+        characterSet: 字符集编码
+        """
+        self.key = encode_key
+        self.mode = mode
+        self.iv = iv_key
+        self.characterSet = characterSet
+        self.paddingMode = paddingMode
+        self.data = ""
+
+    @classmethod
+    def __zero_padding(cls, data):
+        data += b'\x00'
+        while len(data) % 16 != 0:
+            data += b'\x00'
+        return data
+
+    @classmethod
+    def __strip_zero_padding(cls, data):
+        data = data[:-1]
+        while len(data) % 16 != 0:
+            data = data.rstrip(b'\x00')
+            if data[-1] != b"\x00":
+                break
+        return data
+
+    def __PKCS5_7Padding(self, data):
+        needSize = 16 - len(data) % 16
+        if needSize == 0:
+            needSize = 16
+        return data + needSize.to_bytes(1, 'little') * needSize
+
+    def __StripPKCS5_7Padding(self, data):
+        paddingSize = data[-1]
+        return data.rstrip(paddingSize.to_bytes(1, 'little'))
+
+    def __paddingData(self, data):
+        if self.paddingMode == "NoPadding":
+            if len(data) % 16 == 0:
+                return data
+            else:
+                return self.__zero_padding(data)
+        elif self.paddingMode == "ZeroPadding":
+            return self.__zero_padding(data)
+        elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
+            return self.__PKCS5_7Padding(data)
+        else:
+            print("不支持Padding")
+
+    def __stripPaddingData(self, data):
+        if self.paddingMode == "NoPadding":
+            return self.__strip_zero_padding(data)
+        elif self.paddingMode == "ZeroPadding":
+            return self.__strip_zero_padding(data)
+
+        elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
+            return self.__StripPKCS5_7Padding(data)
+        else:
+            print("不支持Padding")
+
+    def setCharacterSet(self, characterSet):
+        """
+        设置字符集编码
+        characterSet: 字符集编码
+        """
+        self.characterSet = characterSet
+
+    def setPaddingMode(self, mode):
+        """
+        设置填充模式
+        mode: 可选NoPadding,ZeroPadding,PKCS5Padding,PKCS7Padding
+        """
+        self.paddingMode = mode
+
+    def decryptFromBase64(self, entext):
+        """
+        从base64编码字符串编码进行AES解密
+        entext: 数据类型str
+        """
+        mData = MData(characterSet=self.characterSet)
+        self.data = mData.fromBase64(entext)
+        return self.__decrypt()
+
+    def decryptFromHexStr(self, entext):
+        """
+        从hexstr编码字符串编码进行AES解密
+        entext: 数据类型str
+        """
+        mData = MData(characterSet=self.characterSet)
+        self.data = mData.fromHexStr(entext)
+        return self.__decrypt()
+
+    def decryptFromString(self, entext):
+        """
+        从字符串进行AES解密
+        entext: 数据类型str
+        """
+        mData = MData(characterSet=self.characterSet)
+        self.data = mData.fromString(entext)
+        return self.__decrypt()
+
+    def decryptFromBytes(self, entext):
+        """
+        从二进制进行AES解密
+        entext: 数据类型bytes
+        """
+        self.data = entext
+        return self.__decrypt()
+
+    def encryptFromString(self, data):
+        """
+        对字符串进行AES加密
+        data: 待加密字符串,数据类型为str
+        """
+        self.data = data.encode(self.characterSet)
+        return self.__encrypt()
+
+    def __encrypt(self):
+        """
+        加密数据
+        @return:
+        """
+        if self.mode == AES.MODE_CBC:
+            aes = AES.new(self.key, self.mode, self.iv)
+        elif self.mode == AES.MODE_ECB:
+            aes = AES.new(self.key, self.mode)
+        else:
+            print("不支持这种模式")
+            return
+
+        data = self.__paddingData(self.data)
+        enData = aes.encrypt(data)
+        return MData(enData)
+
+    def __decrypt(self):
+        if self.mode == AES.MODE_CBC:
+            aes = AES.new(self.key, self.mode, self.iv)
+        elif self.mode == AES.MODE_ECB:
+            aes = AES.new(self.key, self.mode)
+        else:
+            print("不支持这种模式")
+            return
+        data = aes.decrypt(self.data)
+        mData = MData(self.__stripPaddingData(data), characterSet=self.characterSet)
+        return mData
+
+
+if __name__ == '__main__':
+    key = b"1234567812345678"
+    iv = b"0000000000000000"
+    aes = AESencrypt(key, AES.MODE_CBC, iv, paddingMode="ZeroPadding", characterSet='utf-8')
+
+    str_data = "dddd321123."
+    rData = aes.encryptFromString(str_data)
+    print("加密:", rData.toBase64())
+    rData = aes.decryptFromBase64(rData.toBase64())
+    print("解密:", rData)

+ 66 - 22
Service/CommonService.py

@@ -3,27 +3,29 @@
 import base64
 import datetime
 import time
+from base64 import encodebytes
 from pathlib import Path
 from random import Random
+
+import OpenSSL.crypto as ct
 import ipdb
 import requests
 import simplejson as json
 from django.core import serializers
-from django.http import HttpResponseRedirect
 from django.utils import timezone
 from pyipip import IPIPDatabase
-from Ansjer.config import BASE_DIR, UNICODE_ASCII_CHARACTER_SET, SERVER_DOMAIN_SSL
-import OpenSSL.crypto as ct
-from base64 import encodebytes
+
+from Ansjer.config import BASE_DIR, SERVER_DOMAIN_SSL, CONFIG_INFO, CONFIG_TEST, CONFIG_CN
 from Controller.CheckUserData import RandomStr
-from Model.models import iotdeviceInfoModel, Device_Info
-from Service.ModelService import ModelService
+from Model.models import iotdeviceInfoModel, Device_Info, CountryModel, RegionModel, UIDModel
 
 
 class CommonService:
     # 添加模糊搜索
     @staticmethod
-    def get_kwargs(data={}):
+    def get_kwargs(data=None):
+        if data is None:
+            data = {}
         kwargs = {}
         for (k, v) in data.items():
             if v is not None and v != u'':
@@ -46,7 +48,7 @@ class CommonService:
         key_list = []
         value_list = []
         for k, v in request_dict.items():
-            key_list.append(k[k.index('[')+1:k.index(']')] if 'meta' in k else k)
+            key_list.append(k[k.index('[') + 1:k.index(']')] if 'meta' in k else k)
             if v == 'true':
                 v = True
             elif v == 'false':
@@ -72,8 +74,10 @@ class CommonService:
         return mb_size
 
     @staticmethod
-    def get_param_flag(data=[]):
+    def get_param_flag(data=None):
         # print(data)
+        if data is None:
+            data = []
         flag = True
         for v in data:
             if v is None:
@@ -286,17 +290,17 @@ class CommonService:
             return ''
         for i in range(start, end):
             if i == 1:
-                content = RandomStr(3, False)+content+RandomStr(3, False)
+                content = RandomStr(3, False) + content + RandomStr(3, False)
                 content = base64.b64encode(str(content).encode("utf-8")).decode('utf8')
             if i == 2:
-                content = RandomStr(2, False)+str(content)+RandomStr(2, False)
+                content = RandomStr(2, False) + str(content) + RandomStr(2, False)
                 content = base64.b64encode(str(content).encode("utf-8")).decode('utf8')
             if i == 3:
-                content = RandomStr(1, False)+str(content)+RandomStr(1, False)
+                content = RandomStr(1, False) + str(content) + RandomStr(1, False)
                 content = base64.b64encode(str(content).encode("utf-8")).decode('utf8')
         return content
 
-    #把格式化时间转换成时间戳
+    # 把格式化时间转换成时间戳
     @staticmethod
     def str_to_timestamp(str_time=None, format='%Y-%m-%d %H:%M:%S'):
         if str_time:
@@ -305,9 +309,6 @@ class CommonService:
             return int(result)
         return int(time.time())
 
-
-
-
     # 把时间戳转换成格式化
     @staticmethod
     def timestamp_to_str(timestamp=None, format='%Y-%m-%d %H:%M:%S'):
@@ -318,8 +319,7 @@ class CommonService:
         else:
             return time.strptime(format)
 
-
-    #计算N个月后的时间戳
+    # 计算N个月后的时间戳
     @staticmethod
     def calcMonthLater(addMonth, unix_timestamp=None):
         if unix_timestamp:
@@ -346,8 +346,8 @@ class CommonService:
         for is_format in range(4):
             try:
                 date_format = '{now_year}-{now_month}-{now_day} {now_hour}:{now_min}:{now_second}' \
-                    .format(now_year=now_year,now_month=now_month,now_day=now_day,now_hour=now_hour,
-                            now_min=now_min,now_second=now_second)
+                    .format(now_year=now_year, now_month=now_month, now_day=now_day, now_hour=now_hour,
+                            now_min=now_min, now_second=now_second)
                 timestamps = CommonService.str_to_timestamp(date_format)
             except Exception as e:
                 if str(e) == 'day is out of range for month':
@@ -476,7 +476,13 @@ class CommonService:
 
     @staticmethod
     def req_publish_mqtt_msg(thing_name, topic_name, msg):
-        # 通用发布MQTT消息函数
+        """
+        通用发布MQTT消息函数
+        @param thing_name: 物品名
+        @param topic_name: 主题名
+        @param msg: 消息内容
+        @return: boolean
+        """
         if not all([thing_name, topic_name, msg]):
             return False
 
@@ -582,12 +588,50 @@ GCqvlyw5dfxNA+EtxNE2wCW/LW7ENJlACgcfgPlBZtpLheWoZB/maw4=
                 return uid
         return serial_number
 
+    @staticmethod
+    def get_full_serial_number(uid, serial_number, device_type):
+        """
+        根据uid查询返回完整序列号
+        @param uid: uid
+        @param serial_number: 序列号
+        @param device_type: 设备类型
+        @return: full_serial_number
+        """
+        p2p_type = str(UIDModel.objects.filter(uid=uid).values('p2p_type')[0]['p2p_type'])
+        # 设备类型转为16进制并补齐4位
+        device_type = hex(device_type)[2:]
+        device_type = (4 - len(device_type)) * '0' + device_type
+        full_serial_number = serial_number + p2p_type + device_type
+        return full_serial_number
+
     # 根据企业标识返回物品名
     @staticmethod
     def get_thing_name(company_mark, thing_name_suffix):
         if company_mark == '11A':
             return 'Ansjer_Device_' + thing_name_suffix
         elif company_mark == '11L':
-            return 'Loocam_Device_' + thing_name_suffix
+            return 'LC_' + thing_name_suffix
         else:
             return thing_name_suffix
+
+    @staticmethod
+    def confirm_region_id(request):
+        """
+        根据配置信息确定region_id
+        @param request: 请求体
+        @return: region_id
+        """
+        region_id = 3
+        if CONFIG_INFO == CONFIG_TEST or CONFIG_INFO == CONFIG_CN:
+            region_id = 1
+        else:  # 国外配置暂时通过ip确认
+            ip = CommonService.get_ip_address(request)
+            ipInfo = CommonService.getIpIpInfo(ip, 'CN')
+            if ipInfo['country_code']:
+                country_qs = CountryModel.objects.filter(country_code=ipInfo['country_code']).values('region__id')
+                if country_qs.exists():
+                    region_id = country_qs[0]['region__id']
+            else:  # 不存在默认返回美洲地区api
+                region_qs = RegionModel.objects.filter(continent_code='NA').values("id")
+                region_id = region_qs[0]['id']
+        return region_id

+ 90 - 4
Service/EquipmentInfoService.py

@@ -7,8 +7,9 @@
 @Software: PyCharm
 """
 import datetime
+import logging
 import time
-
+import itertools
 from django.db.models import Value, CharField
 
 from Model.models import EquipmentInfoMonday, EquipmentInfoTuesday, EquipmentInfoWednesday, EquipmentInfoThursday, \
@@ -165,10 +166,9 @@ class EquipmentInfoService:
         if user_id:
             qs = qs.filter(device_user_id=user_id)
         if event_type:
-            # 兼容AI查询
+            # 多类型查询
             if ',' in event_type:
-                eventTypeList = event_type.split(',')
-                eventTypeList = [int(i.strip()) for i in eventTypeList]
+                eventTypeList = cls.get_comb_event_type(event_type)
                 qs = qs.filter(event_type__in=eventTypeList)
             else:
                 qs = qs.filter(event_type=event_type)
@@ -220,3 +220,89 @@ class EquipmentInfoService:
             item.pop('border_coords')
             item.pop('tab_val')
         return qs_page
+
+    @classmethod
+    def get_comb_event_type(cls, event_type):
+        """
+        重新组合ai消息类型查询,使其支持ai多标签查询
+        @param event_type: 消息类型
+        @return: event_type_list 消息类型数组
+        """
+        event_type_list = event_type.split(',')
+        event_type_list = [int(i.strip()) for i in event_type_list]
+        ai_event_type_list = []
+        for key, val in enumerate(event_type_list):
+            if val <= 4:  # 分离出ai类型,以便后续组合ai标签,目前只存在4个ai类型1,2,3,4
+                ai_event_type_list.append(val)
+                del (event_type_list[key])
+        if len(ai_event_type_list) < 1:
+            return event_type_list
+        ai_event_type_list.sort()
+        type = [1, 2, 3, 4]  # AI目前所有的标签,1人,2车,3宠物,4包裹,后续有新类型需要这里加, 后续会优化,存在表里,包裹存对应的aws标签
+        comb_ai_event_type = []
+        seen = set()
+        for i in range(1, len(type) + 1):  # 计算所有组合,如[1, 2, 3, 4], 4取1,4取2,4取3,4取4
+            for s in itertools.combinations(type, i):
+                if s not in seen:  # 去除重复项, 如a=[1,2,3,4,4],会有两个[1,2,3,4,4],[1,2,3,4,4]的组合
+                    seen.add(s)
+                    s_list = list(s)
+                    for ai_event_type in ai_event_type_list:
+                        if ai_event_type in s_list:  # 排除没有选择的标签组合
+                            if s_list not in comb_ai_event_type:
+                                s_list = [str(v) for v in s_list]
+                                comb_ai_event_type.append(s_list)
+        regroup_list = []
+        for val in comb_ai_event_type:  # 组合ai类型组合,如[[2,3],[1,3]] -> [23, 13]
+            val = ''.join(val)
+            regroup_list.append(int(val))
+        event_type_list = regroup_list + event_type_list  # 加上普通移动消息类型
+        return event_type_list
+
+    @classmethod
+    def get_all_comb_event_type(cls):
+        """
+        计算ai消息类型全组合
+        @return: event_type_list ai所有消息类型数组
+        """
+        type = [1, 2, 3, 4]  # AI目前所有的标签,1人,2车,3宠物,4包裹,后续有新类型需要这里加, 后续会优化,存在表里,包裹存对应的aws标签
+        comb_ai_event_type = []
+        for i in range(1, len(type) + 1):  # 计算所有组合,如[1, 2, 3, 4], 4取1,4取2,4取3,4取4
+            for s in itertools.combinations(type, i):
+                    s_list = list(s)
+                    s_list = [str(v) for v in s_list]
+                    comb_ai_event_type.append(s_list)
+        regroup_list = []
+        for val in comb_ai_event_type:  # 组合ai类型组合,如[[2,3],[1,3]] -> [23, 13]
+            val = ''.join(val)
+            regroup_list.append(int(val))
+        return regroup_list
+
+    @staticmethod
+    def get_equipment_info_obj(dt, **kwargs):
+        """
+        根据日期判断是星期几,返回相应的对象实例
+        @param dt: 日期 例:2022-03-03
+        @param kwargs: 设备信息属性值
+        @return: 星期一至星期天equipment_info对象实例
+        """
+        logger = logging.getLogger('info')
+        week = LocalDateTimeUtil.date_to_week(dt)
+        logger.info('本周{}'.format(str(week)))
+        equipment_info = None
+        if week == 1:
+            equipment_info = EquipmentInfoMonday(**kwargs)
+        elif week == 2:
+            equipment_info = EquipmentInfoTuesday(**kwargs)
+        elif week == 3:
+            equipment_info = EquipmentInfoWednesday(**kwargs)
+        elif week == 4:
+            equipment_info = EquipmentInfoThursday(**kwargs)
+        elif week == 5:
+            equipment_info = EquipmentInfoFriday(**kwargs)
+        elif week == 6:
+            equipment_info = EquipmentInfoSaturday(**kwargs)
+        elif week == 7:
+            equipment_info = EquipmentInfoSunday(**kwargs)
+        logger.info(type(equipment_info))
+        logger.info(equipment_info)
+        return equipment_info

+ 1 - 1
Service/MiscellService.py

@@ -34,9 +34,9 @@ class MyserviceDynamodb():
     def __session(self):
         try:
             session = Session(aws_access_key_id=self.access_key, aws_secret_access_key=self.secret_key,region_name=self.region)
+            return session
         except:
             print("Failed to connect session in region{0}".format(self.region))
-        return session
 
     # 添加access_log表数据
     def access_log_item_put(self,table_name,data_list):

+ 181 - 0
Service/PayService.py

@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+# 支付类
+
+
+class PaymentService:
+
+    # 返回支付失败html内容
+    @staticmethod
+    def get_pay_error_content():
+        return '''
+<!DOCTYPE html>
+<html>
+<head>
+	<!--浏览器不缓存-->
+	<meta http-equiv="Pragma" content="no-cache">
+	<meta http-equiv="Cache-Control" content="no-cache">
+	<meta http-equiv="Expires" content="0">
+	<!--utf-8-->
+    <meta http-equiv="content-type" content="text/html;charset=utf-8">
+    <!-- viewport的<meta>标签,这个标签可以修改在大部分的移动设备上面的显示,为了确保适当的绘制和触屏缩放。-->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <link rel="shortcut icon" href="https://test.zositechc.cn/web/images/favicon.ico" type="image/x-icon"  charset="utf-8"/>
+    <title>Trading particulars</title>
+    <style>
+    	.title_head{
+    		height: 50px;
+    		border-radius: 5px;
+    		background-color: #c3c6c7;
+    		text-align: center;
+    		line-height: 50px;
+    	}
+    	.content{
+    		text-align: center;
+    		margin-top: 50px;
+    		font-size: 20px;
+    		color : #ec7648
+    	}
+    	.content_img{
+    		width: 60px;
+    		height: 60px;
+    	}
+    	.bottom{
+    		 margin-bottom: 10px;
+    		 margin-top: 250px;
+    		 color : #ec7648
+    	}
+    	.bottom_div{
+    		border: 1px solid #ec7648;
+    		line-height: 38px;
+    		text-align: center;
+    		width: 100px;
+    		height: 38px;
+    		border-radius: 5px;
+    	}
+
+    	.bottom_div:hover{
+    		background-color: #dde4e2;
+    	}
+    </style>
+</head>
+<body>
+	<div class="title_head">Trading particulars</div>
+    <div class="content">
+    	<p >
+    		<img src="https://test.zositechc.cn/web/images/failed.jpg" class="content_img">
+    		<br />
+    		Payment failure
+    	</p>
+    </div>
+    <center class="bottom">
+    	<div class="bottom_div" onclick="payOKButton()">
+    	 Finish
+    	</div>
+    </center>
+    <script> 	    // 点击付款成功按钮
+    function payOKButton() {
+        // 复杂数据
+        console.log('success')
+        window.location.href="https://www.baidu.com?page=closePage";
+    }
+	</script>
+</body>
+</html>
+                '''
+
+    # 返回支付成功html内容
+    @staticmethod
+    def get_pay_ok_content(lang, pay_type):
+        title = "支付成功"
+        complete = '完成'
+        if lang == 'cn':
+            if pay_type == "10":
+                title = "体验成功"
+            if pay_type == "11":
+                title = "兑换成功"
+        else:
+            title = "Payment successful"
+            complete = 'complete'
+            if pay_type == "10":
+                title = "Successful experience"
+            if pay_type == "11":
+                title = "Successful exchange"
+
+        return '''
+        <html>
+        <head>
+                <!--浏览器不缓存-->
+                <meta http-equiv="Pragma" content="no-cache">
+                <meta http-equiv="Cache-Control" content="no-cache">
+                <meta http-equiv="Expires" content="0">
+                <!--utf-8-->
+            <meta http-equiv="content-type" content="text/html;charset=utf-8">
+            <!-- viewport的<meta>标签,这个标签可以修改在大部分的移动设备上面的显示,为了确保适当的绘制和触屏缩放。-->
+            <meta name="viewport" content="width=device-width, initial-scale=1.0">
+            <link rel="shortcut icon" href="https://test.zositechc.cn/web/images/favicon.ico" type="image/x-icon" charset="utf-8">
+            <title>''' + title + '''</title>
+            <style>
+                    .title_head{
+                            height: 50px;
+                            border-radius: 5px;
+                            background-color: #c3c6c7;
+                            text-align: center;
+                            line-height: 50px;
+                    }
+                    .content{
+                            text-align: center;
+                            margin-top: 50px;
+                            font-size: 15px;
+                            color:#0000008A;
+
+                    }
+                    .content_img{
+        					margin-bottom:15px;
+                            width: 60px;
+                            height: 60px;
+                    }
+                    .bottom{
+                             margin-bottom: 10px;
+                             margin-top: 250px;
+                             color : white;
+                    }
+                    .bottom_div{
+                            border: 1px solid #68c9c5;
+                            line-height: 38px;
+                            text-align: center;
+                            width: 100px;
+                            height: 38px;
+                            border-radius: 30px;
+        					background-color:#68c9c5;
+                    }
+
+                    .bottom_div:hover{
+                            background-color: #dde4e2;
+                    }
+            </style>
+        </head>
+        <body style="" rlt="1" inmaintabuse="true">
+
+            <div class="content">
+                    <p>
+        					<img src="https://test.zositechc.cn/web/images/success.png" class="content_img">
+                            <br>
+                            ''' + title + '''
+                    </p>
+            </div>
+            <center class="bottom">
+                    <div class="bottom_div" onclick="payOKButton()">
+                     ''' + complete + '''
+                    </div>
+            </center>
+            <script src="//hm.baidu.com/hm.js?eaa57ca47dacb4ad4f5a257001a3457c"></script><script>             // 点击付款成功按钮
+            function payOKButton() {
+                // 复杂数据
+                console.log('success')
+                window.location.href="https://www.baidu.com?page=closePage"
+            }
+                </script>
+
+
+                <div id="qds" style="display:none;"></div></body></html>
+                '''

BIN
requirements.txt