Forráskód Böngészése

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

locky 3 éve
szülő
commit
548918967d

+ 34 - 4
AdminController/DeviceManagementController.py

@@ -16,7 +16,7 @@ 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
+    VodHlsModel, ExperienceContextModel, DeviceTypeModel, Equipment_Info, UidUserModel, ExperienceAiModel, AiService
 
 
 class DeviceManagement(View):
@@ -51,8 +51,10 @@ class DeviceManagement(View):
                 return self.getDeviceInfoList(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':
@@ -182,14 +184,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 +201,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)

+ 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))
 

+ 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'

+ 20 - 0
Ansjer/server_urls/loocam_url.py

@@ -0,0 +1,20 @@
+# -*- 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 SensorGateway import EquipmentFamilyController, GatewayFamilyRoomController, GatewayFamilyMemberController, \
+    SubDeviceController
+
+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()),
+]

+ 14 - 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 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,10 @@ 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")),
+
     # 传感器网关
     re_path('sensorGateway/(?P<operation>.*)', SensorGatewayController.SensorGateway.as_view()),
 

+ 197 - 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,8 +358,10 @@ 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]
             ai_service_data['endTime'] = sum_end_time
@@ -376,13 +370,100 @@ class AiView(View):
             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 +481,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 +506,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 +674,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 +717,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 +754,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 +772,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 +850,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 +897,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 +904,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 +936,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 +985,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 +1005,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 +1027,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 +1078,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)

+ 13 - 181
Controller/CloudStorage.py

@@ -60,6 +60,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 +626,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 +734,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 +884,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 +1014,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 +1103,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 +1137,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})

+ 58 - 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,79 @@ 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']
 
         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)
+            # CouponLang.objects.create(
+            #     lang='en',
+            #     instruction='for the first month of the Auto-renewal plan',
+            #     quota='20.0%',
+            #     unit='off',
+            #     remark='This coupon can be used on each device once only.'
+            # )
+            # CouponConfigModel.objects.get(id=1).lang.add(1)
+            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=1,
+                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})
 

+ 19 - 46
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
@@ -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':
@@ -230,7 +231,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 +244,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))

+ 1 - 1
Controller/PaymentCycle.py

@@ -269,7 +269,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)

+ 294 - 67
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,7 +890,8 @@ 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套餐表')
+    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')
     nickname = models.CharField(default='', max_length=64, verbose_name='设备昵称')
     uid_bucket_id = models.IntegerField(default=0, verbose_name='关联uid_bucket的字段')
@@ -909,16 +916,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 +972,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 +990,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 +1014,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 +1023,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 +1062,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 +1092,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 +1101,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 +1159,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 +1185,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 +1431,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 +1466,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 +1497,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 +1512,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 +1556,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 +1584,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 +1598,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 +1609,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')
@@ -1640,6 +1707,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 +1718,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 +1762,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 +1788,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 +1963,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 +1976,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 +1986,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 +2001,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 +2026,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 +2042,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 +2067,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 +2103,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 +2152,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'
@@ -2144,16 +2219,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 +2339,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 +2349,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 +2377,152 @@ 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'设备名称')
+    src_addr = models.CharField(default='', max_length=16, verbose_name=u'短地址')
+    status = models.SmallIntegerField(default=0, verbose_name='状态')  # 0:关闭, 1:开启
+    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.ForeignKey(GatewaySubDevice, to_field='id', default='', on_delete=models.CASCADE,
+                                   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:同意}')
+    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
+

+ 850 - 0
SensorGateway/EquipmentFamilyController.py

@@ -0,0 +1,850 @@
+# -*- 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
+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 == 'family-save':
+            return self.family_save(user_id, request_dict, response)
+        # 家庭设置
+        elif operation == 'family-setting':
+            return self.get_family_setting(user_id, 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)
+        else:
+            return response.json(414)
+
+    @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, userID_id=user_id)
+        if device_info_qs:
+            # 判断设备是否已存在
+            if device_info_qs[0].isExist == 1:
+                return response.json(174)
+            else:
+                device_info_qs.delete()
+        try:
+            with transaction.atomic():
+                # 格式化后的日期时间
+                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)
+        # 获取设备信息列表
+        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:
+            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 = []
+                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 response.json(0, items)
+        return response.json(309)
+
+    @classmethod
+    def get_family_setting(cls, user_id, request_dict, response):
+        """
+        家庭设置
+        @param user_id: 用户id
+        @param request_dict: 请求
+        @param response: 响应
+        @return: 家庭列表items
+        """
+        family_id = request_dict.get('familyId', None)
+        if not family_id:
+            return response.json(444)
+        user_family_qs = UserFamily.objects.filter(id=family_id, user_id=user_id).values('id', 'name', 'location')
+        if not user_family_qs.exists():
+            return response.json(404)
+        family_dict = user_family_qs.first()
+        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')
+        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']
+            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': 0})
+        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():
+                room_dict = {'updated_time': now_time, 'name': room_name}
+                room_qs.update(**room_dict)
+            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

+ 105 - 0
SensorGateway/GatewayFamilyMemberController.py

@@ -0,0 +1,105 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : GatewayFamilyMemberController.py
+@Time    : 2022/5/25 10:21
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+
+from django.views.generic.base import View
+
+from Model.models import FamilyMemberJoin, UserFamily
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from SensorGateway.EquipmentFamilyController import EquipmentFamilyView
+
+
+# 家庭房间管理
+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)
+
+    @classmethod
+    def member_join(cls, app_user_id, request_dict, response):
+        pass
+
+    @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('status', 'user__username',
+                                                                                  'user__userIconUrl', 'user__phone',
+                                                                                  'user__userEmail', 'updated_time',
+                                                                                  'created_time')
+
+        page_no = int(page_no)
+        page_size = int(page_size)
+        member_join = member_join.order_by('-created_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({
+                'status': item['status'],
+                'userName': item['user__username'],
+                'userIconUrl': item['user__userIconUrl'],
+                'phone': item['user__phone'],
+                'userEmail': item['user__userEmail'],
+                'updatedTime': item['updated_time'],
+                'createdTime': item['created_time']
+            })
+        return response.json(0, result)
+
+    @classmethod
+    def family_qrcode(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)
+        is_owner = EquipmentFamilyView.get_family_owner(user_id, family_id)
+        if not is_owner:
+            return response.json(404)
+        try:
+            UserFamily.objects.get(id=family_id)
+        except Exception as e:
+            print(e)
+            return response.json(173, repr(e))
+        pass

+ 193 - 0
SensorGateway/GatewayFamilyRoomController.py

@@ -0,0 +1,193 @@
+# -*- 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
+from django.views.generic.base import View
+
+from Model.models import FamilyRoomDevice, FamilyRoom
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from SensorGateway.EquipmentFamilyController import EquipmentFamilyView
+
+
+# 家庭房间管理
+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)
+
+    @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.getlist('deviceIds', None)
+        room_id = request_dict.get('roomId', None)
+        operate = request_dict.get('operate', None)
+        if not all([family_id, device_ids, operate, room_id]):
+            return response.json(444)
+        operate = int(operate)
+        is_owner = EquipmentFamilyView.get_family_owner(app_user_id, family_id)
+        if not is_owner:
+            return response.json(404)
+        with transaction.atomic():
+            room_qs = FamilyRoom.objects.filter(family_id=family_id, id=room_id)
+            if not room_qs.exists():
+                return response.json(173)
+            for item in device_ids:
+                qs = FamilyRoomDevice.objects.filter(family_id=family_id, device_id=item)
+                if qs.exists():
+                    qs.update(room_id=0) if operate == 1 else qs.update(room_id=int(room_id))
+            return response.json(0)
+
+    @classmethod
+    def room_del(cls, user_id, request_dict, response):
+        """
+        房间多选删除
+        @param user_id: 当前登录用户id
+        @param request_dict: 请求参数
+        @param response: 响应参数
+        @return:
+        """
+        ids = request_dict.getlist('roomIds', None)
+        if not ids:
+            return response.json(444)
+        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.getlist('ids', None)
+        if not ids:
+            return response.json(444)
+        for item in ids:
+            items = item.split(',')
+            room_id = items[0]
+            sort = items[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)
+        page_no = request_dict.get('pageNo', None)
+        page_size = request_dict.get('pageSize', None)
+        if not all([family_id, room_id, page_no, page_size]):
+            return response.json(444)
+        room_count = FamilyRoomDevice.objects.filter(family_id=int(family_id), room_id=int(room_id)).values(
+            'device_id').annotate(count=Count('device_id')).count()
+        device_room = []
+        # 房间设备列表
+        if room_count > 0:
+            device_room_list = EquipmentFamilyView.get_family_device_list(user_id=app_user_id, page_no=1,
+                                                                          page_size=room_count,
+                                                                          family_id=int(family_id),
+                                                                          room_id=int(room_id))
+
+            if device_room_list:
+                room_name = FamilyRoom.objects.get(id=room_id).name
+
+                for item in device_room_list:
+                    device_room.append({
+                        'deviceId': item['id'],
+                        'deviceType': item['Type'],
+                        'nickName': item['NickName'],
+                        'roomName': room_name,
+                    })
+        device_not_room = []
+        device_not_room_count = FamilyRoomDevice.objects.filter(family_id=int(family_id), room_id=0).values(
+            'device_id').annotate(count=Count('device_id')).count()
+        if device_not_room_count > 0:
+            not_room_device_list = EquipmentFamilyView.get_family_device_list(user_id=app_user_id, page_no=int(page_no),
+                                                                              page_size=int(page_size),
+                                                                              family_id=int(family_id), room_id=0,
+                                                                              is_room_other=True)
+            if not_room_device_list:
+                for item in not_room_device_list:
+                    room_device_qs = FamilyRoomDevice.objects.filter(family_id=int(family_id))
+                    room_device_qs = room_device_qs.filter(~Q(room_id=0))
+                    name = ''
+                    if room_device_qs.exists():
+                        family_room_qs = FamilyRoom.objects.filter(id=room_device_qs.first().room_id)
+                        if family_room_qs.exists():
+                            name = family_room_qs.first().name
+                    device_not_room.append({
+                        'deviceId': item['id'],
+                        'deviceType': item['Type'],
+                        'nickName': item['NickName'],
+                        'roomName': name
+                    })
+        return response.json(0, {'deviceRooms': device_room, 'deviceNotRooms': device_not_room})

+ 175 - 0
SensorGateway/SubDeviceController.py

@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+"""
+@Time : 2022/5/25 15:17
+@Auth : Locky
+@File :SubDeviceController.py
+@IDE :PyCharm
+"""
+import time
+
+from django.db import transaction
+from django.views import View
+
+from Model.models import Device_Info, GatewaySubDevice, FamilyRoomDevice
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+
+
+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 = 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
+        user_id = '154700384179113800138000'
+        if operation == 'add':  # 添加子设备
+            return self.add(request_dict, user_id, response)
+        elif operation == 'query':  # 查询子设备
+            return self.query(request_dict, user_id, response)
+        elif operation == 'update':  # 更新子设备信息
+            return self.update(request_dict, user_id, response)
+        elif operation == 'delete':  # 删除子设备
+            return self.delete(request_dict, user_id, response)
+        else:
+            return response.json(414)
+
+    @staticmethod
+    def add(request_dict, user_id, response):
+        """
+        添加子设备
+        @param request_dict: 请求参数
+        @request_dict serial_number: 序列号
+        @request_dict device_type: 设备类型
+        @request_dict nickname: 设备名
+        @request_dict src_addr: 短地址
+        @request_dict family_id: 家庭id
+        @request_dict room_id: 房间id
+        @param user_id: 用户id
+        @param response: 响应对象
+        @return: response 响应对象
+        """
+        serial_number = request_dict.get('serial_number', None)
+        device_type = int(request_dict.get('device_type', None))
+        nickname = request_dict.get('nickname', None)
+        src_addr = request_dict.get('src_addr', None)
+        family_id = request_dict.get('family_id', None)
+        room_id = request_dict.get('room_id', None)
+
+        if not all([serial_number, device_type, nickname, src_addr, family_id, room_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']
+            with transaction.atomic():
+                sub_device = GatewaySubDevice.objects.create(device_id=device_id, device_type=device_type,
+                                                             nickname=nickname, src_addr=src_addr, status=1,
+                                                             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, 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(request_dict, user_id, response):
+        """
+        查询子设备
+        @param request_dict: 请求参数
+        @request_dict serial_number: 序列号
+        @param user_id: 用户id
+        @param response: 响应对象
+        @return: response
+        """
+        serial_number = request_dict.get('serial_number', None)
+
+        if not all([serial_number]):
+            return response.json(444)
+        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']
+            count = GatewaySubDevice.objects.filter(device_id=device_id).count()
+            gateway_sub_device_qs = GatewaySubDevice.objects.filter(device_id=device_id).values('device_type',
+                                                                                                'nickname', 'src_addr',
+                                                                                                'status')
+            gateway_sub_device_list = [gateway_sub_device for gateway_sub_device in gateway_sub_device_qs]
+            res = {
+                'count': count,
+                'gateway_sub_device_list': gateway_sub_device_list
+            }
+            return response.json(0, res)
+        except Exception as e:
+            return response.json(500, repr(e))
+
+    @staticmethod
+    def update(request_dict, user_id, response):
+        """
+        更新子设备信息
+        @param request_dict: 请求参数
+        @request_dict serial_number: 序列号
+        @request_dict sub_device_id: 子设备id
+        @param user_id: 用户id
+        @param response: 响应对象
+        @return: response
+        """
+        serial_number = request_dict.get('serial_number', None)
+        sub_device_id = request_dict.get('sub_device_id', None)
+        nickname = request_dict.get('nickname', None)
+        src_addr = request_dict.get('src_addr', None)
+        status = int(request_dict.get('status', None))
+        family_id = request_dict.get('family_id', None)
+        room_id = request_dict.get('room_id', None)
+
+        if not all([serial_number]):
+            return response.json(444)
+        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)
+            update_data = {
+                'nickname': nickname,
+                'src_addr': src_addr,
+                'status': status,
+            }
+            GatewaySubDevice.objects.filter(id=sub_device_id).update(**update_data)
+            return response.json(0)
+        except Exception as e:
+            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('sub_device_id', 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))
+

+ 7 - 1
Service/CommonService.py

@@ -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
 

+ 59 - 4
Service/EquipmentInfoService.py

@@ -8,7 +8,7 @@
 """
 import datetime
 import time
-
+import itertools
 from django.db.models import Value, CharField
 
 from Model.models import EquipmentInfoMonday, EquipmentInfoTuesday, EquipmentInfoWednesday, EquipmentInfoThursday, \
@@ -165,10 +165,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 +219,59 @@ 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

+ 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>
+                '''