Browse Source

合并测试服代码(除AI)

locky 2 years ago
parent
commit
a917d0a4a5
65 changed files with 4503 additions and 1843 deletions
  1. 8 0
      AnsjerPush/cn_config/__init__.py
  2. 49 22
      AnsjerPush/cn_config/cn_formal_config.py
  3. 2 2
      AnsjerPush/cn_config/cn_formal_settings.py
  4. 1 1
      AnsjerPush/cn_config/cn_formal_wsgi.py
  5. 25 134
      AnsjerPush/config.py
  6. 8 0
      AnsjerPush/dev_config/__init__.py
  7. 27 1
      AnsjerPush/dev_config/local_config.py
  8. 1 1
      AnsjerPush/dev_config/local_settings.py
  9. 1 1
      AnsjerPush/dev_config/local_wsgi.py
  10. 8 0
      AnsjerPush/eur_config/__init__.py
  11. 49 8
      AnsjerPush/eur_config/eur_formal_config.py
  12. 1 1
      AnsjerPush/eur_config/eur_formal_settings.py
  13. 1 1
      AnsjerPush/eur_config/eur_formal_wsgi.py
  14. 0 3
      AnsjerPush/file/alipay/alipay_private_2048.pem
  15. 0 3
      AnsjerPush/file/alipay/alipay_public_2048.pem
  16. 0 3
      AnsjerPush/file/alipay/zosi_alipay_private_2048.pem
  17. 0 3
      AnsjerPush/file/alipay/zosi_alipay_public_2048.pem
  18. BIN
      AnsjerPush/file/font/simhei.ttf
  19. 0 184
      AnsjerPush/file/log type .txt
  20. 0 69
      AnsjerPush/file/后台管理系统说明.txt
  21. BIN
      AnsjerPush/file/表文档.doc
  22. 8 0
      AnsjerPush/test_config/__init__.py
  23. 0 0
      AnsjerPush/test_config/test.py
  24. 48 32
      AnsjerPush/test_config/test_config.py
  25. 2 1
      AnsjerPush/test_config/test_settings.py
  26. 1 1
      AnsjerPush/test_config/test_wsgi.py
  27. 6 6
      AnsjerPush/urls.py
  28. 8 0
      AnsjerPush/us_config/__init__.py
  29. 49 22
      AnsjerPush/us_config/formal_config.py
  30. 1 1
      AnsjerPush/us_config/formal_settings.py
  31. 1 1
      AnsjerPush/us_config/formal_wsgi.py
  32. 1 83
      Controller/AiController.py
  33. 22 6
      Controller/ComboCron/ComboCronPushController.py
  34. 178 367
      Controller/DetectController.py
  35. 90 289
      Controller/DetectControllerV2.py
  36. 69 177
      Controller/PowerWarningController.py
  37. 21 3
      Controller/ShadowController.py
  38. 76 21
      Controller/gatewayController.py
  39. 2 12
      Model/models.py
  40. 12 42
      Object/ETkObject.py
  41. 20 12
      Object/ResponseObject.py
  42. 66 2
      Service/CommonService.py
  43. 542 0
      Service/DevicePushService.py
  44. 0 107
      Service/GatewayService.py
  45. 97 0
      Service/HuaweiPushService/HuaweiPushService.py
  46. 71 0
      Service/HuaweiPushService/push_admin/__init__.py
  47. 190 0
      Service/HuaweiPushService/push_admin/_app.py
  48. 55 0
      Service/HuaweiPushService/push_admin/_http.py
  49. 613 0
      Service/HuaweiPushService/push_admin/_message_serializer.py
  50. 915 0
      Service/HuaweiPushService/push_admin/_messages.py
  51. 224 0
      Service/HuaweiPushService/push_admin/messaging.py
  52. 433 0
      Service/PushService.py
  53. 91 0
      Service/VivoPushService/push_admin/APIConstants.py
  54. 13 0
      Service/VivoPushService/push_admin/APIError.py
  55. 103 0
      Service/VivoPushService/push_admin/APIMessage.py
  56. 73 0
      Service/VivoPushService/push_admin/APISender.py
  57. 131 0
      Service/VivoPushService/push_admin/APISenderBase.py
  58. 17 0
      Service/VivoPushService/push_admin/APISignUtil.py
  59. 0 0
      Service/VivoPushService/push_admin/__init__.py
  60. 1 1
      cn_formal_manage.py
  61. 1 1
      eur_formal_manage.py
  62. 1 1
      formal_manage.py
  63. 1 1
      local_manage.py
  64. 68 216
      requirements.txt
  65. 1 1
      test_manage.py

+ 8 - 0
AnsjerPush/cn_config/__init__.py

@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : __init__.py.py
+@Time    : 2022/11/29 10:41
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""

+ 49 - 22
AnsjerPush/cn_formal_config.py → AnsjerPush/cn_config/cn_formal_config.py

@@ -1,22 +1,30 @@
-#!/usr/bin/env python3  
-# -*- coding: utf-8 -*-  
 """
-@Copyright (C) ansjer cop Video Technology Co.,Ltd.All rights reserved.
-@AUTHOR: ASJRD018
-@NAME: Ansjer
-@software: PyCharm
-@DATE: 2018/7/2 14:06
-@Version: python3.6
-@MODIFY DECORD:ansjer dev
-@file: Conf.py
-@Contact: chanjunkai@163.com
+独立于config.py的配置文件
 """
-# 主要静态变量配置文件
-import datetime, os
+import os
+import datetime
 
+# 配置信息
+CONFIG_INFO = 'cn'
+
+"""
+AWS相关
+"""
+# ======================================================================================================================
+# aws api key
+AWS_ARN_S3 = 'arn:aws-cn:s3'
+REGION_NAME = 'cn-northwest-1'
+ACCESS_KEY_ID = 'AKIA2MMWBR4DSFG67DTG'
+SECRET_ACCESS_KEY = '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'
+
+# 存储桶
+PUSH_BUCKET = 'push'                                # 推送存储桶
+
+# redis节点
+REDIS_ADDRESS = 'pushredis.3xavzq.0001.cnw1.cache.amazonaws.com.cn'
+
+APNS_MODE = 'prod'
 
-DEBUG_MODE = 'DEV'
-# MODE = 'PRO'
 # 阿里云发邮箱
 ALY_SES_ACCESS_NAME = 'message@dvema.com'
 ALY_SES_ACCESS_PAW = 'SMtp123456'
@@ -46,8 +54,6 @@ TX_PHONE_APP_KEY = '7705976ca6e85fe7b86d6bc2d11f7783'
 # 验证码超时时间
 AuthCode_Expire = 600
 
-# 根路径
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 # uid token key
 UID_TOKEN_KEY = 'c+565*j@%^'
 
@@ -56,8 +62,6 @@ OSS_STS_ACCESS_KEY = 'LTAIyMkGfEdogyL9'
 OSS_STS_ACCESS_SECRET = '71uIjpsqVOmF7DAITRyRuc259jHOjO'
 OSS_ROLE_ARN = 'acs:ram::1901342792446414:role/stsoss'
 
-SERVER_TYPE = 'Ansjer.cn_formal_settings'
-
 NGINX_RTMP_STAT = 'http://www.dvema.com/stat'
 SERVER_DOMAIN = 'http://www.dvema.com/'
 SERVER_DOMAIN_SSL = 'https://www.dvema.com/'
@@ -169,6 +173,29 @@ APNS_CONFIG = {
         'pem_path': 'AnsjerPush/file/apns_pem/commissionf.pem',
     }
 }
-APNS_MODE = 'prod'
-REDIS_ADDRESS = 'pushredis.3xavzq.0001.cnw1.cache.amazonaws.com.cn'
-# REDIS_ADDRESS = '192.168.136.45'
+
+XMPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': 'fXAdAwGDum3FKgQtAiW9hg=='
+}
+
+VIVOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID':'102227506',
+        'Key':'531bf5befece8840d31b654a166db91c',
+        'Secret':'9d6f19ef-5cdb-47ee-9433-d74c7d11fa50'
+    }
+}
+
+OPPOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': {
+        'Key': '882266a2000e4407990932be0b7043f5',
+        'Secret': '1037f29469c8416e87ab9cb8537c68ce'
+    }
+}
+
+MEIZUPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID': 151022,
+        'AppSecret': '890e94e09b7b4aa18278acce82e35c46',
+    }
+}

+ 2 - 2
AnsjerPush/cn_formal_settings.py → AnsjerPush/cn_config/cn_formal_settings.py

@@ -1,6 +1,6 @@
 import os
 
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 SECRET_KEY = '$2hf5g$a%_^kk0-l25l$!o5)yc=dvtnfpc8(+$rh4fq4twa_xx'
 DEBUG = False
 ALLOWED_HOSTS = ["*"]
@@ -47,7 +47,7 @@ TEMPLATES = [
     },
 ]
 
-WSGI_APPLICATION = 'AnsjerPush.cn_formal_wsgi.application'
+WSGI_APPLICATION = 'AnsjerPush.wsgi.application'
 
 # 业务数据库
 DATABASE_DATA = 'ansjer_server_cn'

+ 1 - 1
AnsjerPush/cn_formal_wsgi.py → AnsjerPush/cn_config/cn_formal_wsgi.py

@@ -11,6 +11,6 @@ import os
 
 from django.core.wsgi import get_wsgi_application
 
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.cn_formal_settings')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.cn_config.cn_formal_settings')
 
 application = get_wsgi_application()

+ 25 - 134
AnsjerPush/config.py

@@ -1,22 +1,20 @@
-#!/usr/bin/env python3  
-# -*- coding: utf-8 -*-  
 """
-@Copyright (C) ansjer cop Video Technology Co.,Ltd.All rights reserved.
-@AUTHOR: ASJRD018
-@NAME: Ansjer
-@software: PyCharm
-@DATE: 2018/7/2 14:06
-@Version: python3.6
-@MODIFY DECORD:ansjer dev
-@file: Conf.py
-@Contact: chanjunkai@163.com
+公用配置文件
 """
-# 主要静态变量配置文件
-import datetime, os
 
+import os
+import datetime
+
+# 配置信息
+CONFIG_TEST = 'test'
+CONFIG_CN = 'cn'
+CONFIG_US = 'us'
+CONFIG_EUR = 'eur'
+
+# 配置模式和根路径
+SERVER_TYPE = os.environ.get('DJANGO_SETTINGS_MODULE')
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
-DEBUG_MODE = 'DEV'
-# MODE = 'PRO'
 # 阿里云发邮箱
 ALY_SES_ACCESS_NAME = 'message@dvema.com'
 ALY_SES_ACCESS_PAW = 'SMtp123456'
@@ -35,7 +33,6 @@ OFF_LINE_TIME_DELTA = 5
 OAUTH_ACCESS_TOKEN_SECRET = 'a+jbgnw%@1%zy^=@dn62%'
 OAUTH_REFRESH_TOKEN_SECRET = 'r+jbgnw%@1%zy^=@dn62%'
 # access_token超时
-# OAUTH_ACCESS_TOKEN_TIME = datetime.timedelta(hours=1)
 OAUTH_ACCESS_TOKEN_TIME = datetime.timedelta(days=30)
 # refresh_token超时
 OAUTH_REFRESH_TOKEN_TIME = datetime.timedelta(days=30)
@@ -46,27 +43,23 @@ TX_PHONE_APP_KEY = '7705976ca6e85fe7b86d6bc2d11f7783'
 # 验证码超时时间
 AuthCode_Expire = 600
 
-# 根路径
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 # uid token key
 UID_TOKEN_KEY = 'c+565*j@%^'
-
 # oss param
 OSS_STS_ACCESS_KEY = 'LTAIyMkGfEdogyL9'
 OSS_STS_ACCESS_SECRET = '71uIjpsqVOmF7DAITRyRuc259jHOjO'
 OSS_ROLE_ARN = 'acs:ram::1901342792446414:role/stsoss'
 
 # aws api key
+AWS_ARN = ['arn:aws-cn:s3', 'arn:aws:s3']
 AWS_ACCESS_KEY_ID = ['AKIA2MMWBR4DSFG67DTG', 'AKIA2E67UIMD45Y3HL53']  # 0国内, 1国外
 AWS_SECRET_ACCESS_KEY = ['aI9gxcAKPmiGgPy9axrtFKzjYGbvpuytEX4xWweL', 'ckYLg4Lo9ZXJIcJEAKkzf2rWvs8Xth1FCjqiAqUw']
-AWS_ARN = ['arn:aws-cn:s3', 'arn:aws:s3']
-
 
 NGINX_RTMP_STAT = 'http://www.dvema.com/stat'
 SERVER_DOMAIN = 'http://www.dvema.com/'
 SERVER_DOMAIN_SSL = 'https://www.dvema.com/'
 DOMAIN_HOST = 'www.dvema.com'
-# SERVER_HOST = 'localhost'
+
 PAYPAL_CRD = {
     "mode": "live",  # sandbox or live
     "client_id": "AdSRd6WBn-qLl9OiQHQuNYTDFSx0ZX0RUttqa58au8bPzoGYQUrt8bc6591RmH8_pEAIPijdvVYSVXyI",
@@ -74,111 +67,17 @@ PAYPAL_CRD = {
 }
 DETECT_PUSH_DOMAIN = 'http://push.dvema.com/'
 
-
-JPUSH_CONFIG = {
-    'com.ansjer.accloud_ab': {
-        'Key': 'f0dc047e5e53fd14199de5b0',
-        'Secret': 'aa7f7db33e9f0a7f3871aa1c'},
-    'com.ansjer.adcloud_ab': {
-        'Key': '76d97b535185114985608234',
-        'Secret': 'c9a92b301043cc9c52778692'},
-    'com.ansjer.zccloud_ab': {
-        'Key': 'd9924f56d3cc7c6017965130',
-        'Secret': '869d832d126a232f158b5987'},
-    'com.ansjer.loocamccloud_ab': {
-        'Key': 'd1cc44797b4642b0e05304fe',
-        'Secret': 'c3e8b4ca8c576de61401e56a'},
-    'com.ansjer.loocamdcloud_ab': {
-        'Key': '76d97b535185114985608234',
-        'Secret': 'c9a92b301043cc9c52778692'},
-    'com.ansjer.zccloud_a': {
-        'Key': '57de2a80d68bf270fd6bdf5a',
-        'Secret': '3d354eb6a0b49c2610decf42'},
-    'com.ansjer.accloud_a': {
-        'Key': 'ff95ee685f49c0dc4013347b',
-        'Secret': 'de2c20959f5516fdeeafe78e'},
-    'com.ansjer.adcloud_a': {
-        'Key': '2e47eb1aee9b164460df3668',
-        'Secret': 'b9137d8d684bc248f1809b6d'},
-    'com.ansjer.loocamccloud_a': {
-        'Key': '23c9213215c7ca0ec945629b',
-        'Secret': '81e4b1e859cc8387e2e6c431'},
-    'com.ansjer.loocamdcloud_a': {
-        'Key': '1dbdd60a16e9892d6f68a073',
-        'Secret': '80a97690e7e043109059b403'},
-    'com.ansjer.customizedb_a': {
-        'Key': '9d79630aa49adfa291fe2568',
-        'Secret': '4d8ff52f88136561875a0212'},
-    'com.ansjer.customizedd_a': {
-        'Key': '8fc4f495685bde53341ee25d',
-        'Secret': 'f1da11fa466509fa2670fb66'},
-    'com.ansjer.customizeda_a': {
-        'Key': 'aad8a02b1c4e82b0ef49c559',
-        'Secret': '8798cc7cd63225f13660421a'},
-    'com.ansjer.customizedc_a': {
-        'Key': 'ecdde95cd272f410ee029139',
-        'Secret': '9ddca1e92bfa331126fd8826'},
-    'com.ansjer.customizedf_a': {
-        'Key': '6ddbfb8fd7a0984dfba74a00',
-        'Secret': '971ee5c5facfbe76cb31a686'},
-}
-# type =1
-FCM_CONFIG = {
-    'com.ansjer.zccloud_a': 'AAAAb9YP3rk:APA91bHu8u-CTpcd0g6lKPo0WNVqCi8jZub1cPPbSAY9AucT1HxlF65ZDUko9iG8q2ch17bwu9YWHpK1xI1gHSRXCslLvZlXEmHZC0AG3JKg15XuUvlFKACIajUFV-pOeGRT8tM6-31I',
-    'com.ansjer.loocamccloud_a': 'AAAAb9YP3rk:APA91bFCgd-kbVmpK4EVpfdHH_PJZQCYTkOGnTZdIuBWEz2r7aMRsJYHOH3sB-rwcbaRWgnufTyjX9nGQxb6KxQbWVk4ah_H-M3IqGh6Mb60WQQAuR33V6g_Jes5pGL6ViuIxGHqVMaR',
-    'com.ansjer.loocamdcloud_a': 'AAAAb9YP3rk:APA91bGw2I2KMD4i-5T7nZO_wB8kuAOuqgyqe5rxmY-W5qkpYEx9IL2IfmC_qf6B_xOyjIDDSjckvMo-RauN__SEoxvAkis7042GRkoKpw7cjZ_H8lC-d50PC0GclPzccrOGFusyKbFY',
-    'com.ansjer.customizedb_a': 'AAAAb9YP3rk:APA91bE7kI4vcm-9h_CJNFlOZfc-xwP4Btn6AnjOrwoKV6fgYN7fdarkO76sYxVZiAbDnxsFfOJyP7vQfwyan6mdjuyD5iHdt_XgO22VqniC0vA1V4GJiCS8Tp7LxIX8JVKZl9I_Powt',
-    'com.ansjer.customizeda_a': 'AAAAb9YP3rk:APA91bF0HzizVWDc6dKzobY9fsaKDK4veqkOZehDXshVXs8pEEvNWjR_YWbhP60wsRYCHCal8fWN5cECVOWNMMzDsfU88Ty2AUl8S5FtZsmeDTkoGntQOswBr8Ln7Fm_LAp1VqTf9CpM',
-    'com.ansjer.customizedd_a': 'AAAAb9YP3rk:APA91bHkxOozJWBrlv3eNT0PgwosYENI9aM4Zuzd418cX-iKkpa1zFNC5MkNDKApx1KH4fhmAfaJ6IMRZ0nj5GIxCpstDYCaZWwgC7-etqfSxG5JAq8LOwJx0o_1tUZqwjIic8ztsg0o',
-    'com.ansjer.adcloud_a': 'AAAAb9YP3rk:APA91bFm06w8b9OKQ0gz0iaWFuRqRIkvgAz6z7Gp3dBU_X-LNGJQd1hc1QR2W7QzBglF8SHtERA45a2lbdLRa5qv7hxfd6W_sJLBK7dA8jklsOQBvy505oUzTwMKWy4TwH-exps9KrhO',
-    'com.ansjer.accloud_a': 'AAAAb9YP3rk:APA91bFm06w8b9OKQ0gz0iaWFuRqRIkvgAz6z7Gp3dBU_X-LNGJQd1hc1QR2W7QzBglF8SHtERA45a2lbdLRa5qv7hxfd6W_sJLBK7dA8jklsOQBvy505oUzTwMKWy4TwH-exps9KrhO',
-    'com.ansjer.zccloud_ab': 'AAAAb9YP3rk:APA91bHu8u-CTpcd0g6lKPo0WNVqCi8jZub1cPPbSAY9AucT1HxlF65ZDUko9iG8q2ch17bwu9YWHpK1xI1gHSRXCslLvZlXEmHZC0AG3JKg15XuUvlFKACIajUFV-pOeGRT8tM6-31I',
-    'com.ansjer.customizedc_a': 'AAAAb9YP3rk:APA91bHu8u-CTpcd0g6lKPo0WNVqCi8jZub1cPPbSAY9AucT1HxlF65ZDUko9iG8q2ch17bwu9YWHpK1xI1gHSRXCslLvZlXEmHZC0AG3JKg15XuUvlFKACIajUFV-pOeGRT8tM6-31I',
-    'com.ansjer.customizedf_a': 'AAAAb9YP3rk:APA91bFm06w8b9OKQ0gz0iaWFuRqRIkvgAz6z7Gp3dBU_X-LNGJQd1hc1QR2W7QzBglF8SHtERA45a2lbdLRa5qv7hxfd6W_sJLBK7dA8jklsOQBvy505oUzTwMKWy4TwH-exps9KrhO',
-}
-SERVER_TYPE = os.environ.get('DJANGO_SETTINGS_MODULE')
-
-APNS_CONFIG = {
-    'com.ansjer.loocamccloud': {
-        'pem_path': 'AnsjerPush/file/apns_pem/lcc-dev.pem',
-    },
-    'com.ansjer.zosidcloud': {
-        'pem_path': 'AnsjerPush/file/apns_pem/zosidcloud-dev.pem',
-    },
-    'com.ansjer.customizedb': {
-        'pem_path': 'AnsjerPush/file/apns_pem/customizedb-dev.pem',
-    },
-    'com.ansjer.customizeda': {
-        'pem_path': 'AnsjerPush/file/apns_pem/customizeda-dev.pem',
-    },
-    'com.ansjer.customizede': {
-        'pem_path': 'AnsjerPush/file/apns_pem/customizede.pem',
-    },
-    'com.ansjer.zccloud': {
-        'pem_path': 'AnsjerPush/file/apns_pem/zccloud-dev.pem',
-    },
-    'com.ansjer.accloud': {
-        'pem_path': 'AnsjerPush/file/apns_pem/accloud-dev.pem',
-    },
-    'com.ansjer.customizedc':{
-        'pem_path': 'AnsjerPush/file/apns_pem/customizedc.pem',
-    },
-    'com.ansjer.customizedh': {
-        'pem_path': 'AnsjerPush/file/apns_pem/customizedh.pem',
-    },
-}
 APNS_MODE = 'dev'
-
-if SERVER_TYPE == 'AnsjerPush.local_settings':
-    from AnsjerPush.local_config import *
-elif SERVER_TYPE == 'AnsjerPush.test_settings':
-    from AnsjerPush.test_config import *
-elif SERVER_TYPE == 'AnsjerPush.formal_settings':
-    from AnsjerPush.formal_config import *
-elif SERVER_TYPE == 'AnsjerPush.cn_formal_settings':
-    from AnsjerPush.cn_formal_config import *
-elif SERVER_TYPE == 'AnsjerPush.eur_formal_settings':
-    from AnsjerPush.eur_formal_config import *
+if SERVER_TYPE == 'AnsjerPush.dev_config.local_settings':
+    from AnsjerPush.dev_config.local_config import *
+elif SERVER_TYPE == 'AnsjerPush.test_config.test_settings':
+    from AnsjerPush.test_config.test_config import *
+elif SERVER_TYPE == 'AnsjerPush.us_config.formal_settings':
+    from AnsjerPush.us_config.formal_config import *
+elif SERVER_TYPE == 'AnsjerPush.cn_config.cn_formal_settings':
+    from AnsjerPush.cn_config.cn_formal_config import *
+elif SERVER_TYPE == 'AnsjerPush.eur_config.eur_formal_settings':
+    from AnsjerPush.eur_config.eur_formal_config import *
 
 APNS_CODE = {
     -1: '只库存不推送',
@@ -231,11 +130,3 @@ APP_BUNDLE_DICT = {
     'com.ansjer.customizeda_a': 'Guardian365',
     'com.ansjer.customizedc_a': 'PatrolSecure',
 }
-
-# AI识别标签
-AI_IDENTIFICATION_TAGS_DICT = {
-    '1': 'Person',
-    '2': 'Pet',
-    '3': 'Vehicle',
-    '4': 'Package'
-}

+ 8 - 0
AnsjerPush/dev_config/__init__.py

@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : __init__.py.py
+@Time    : 2022/11/29 13:30
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""

+ 27 - 1
AnsjerPush/local_config.py → AnsjerPush/dev_config/local_config.py

@@ -56,7 +56,7 @@ OSS_STS_ACCESS_KEY = 'LTAIyMkGfEdogyL9'
 OSS_STS_ACCESS_SECRET = '71uIjpsqVOmF7DAITRyRuc259jHOjO'
 OSS_ROLE_ARN = 'acs:ram::1901342792446414:role/stsoss'
 
-SERVER_TYPE = 'Ansjer.local_settings'
+SERVER_TYPE = 'Ansjer.dev_config.local_settings'
 
 NGINX_RTMP_STAT = 'http://www.dvema.com/stat'
 SERVER_DOMAIN = 'http://www.dvema.com/'
@@ -154,5 +154,31 @@ APNS_CONFIG = {
         'pem_path': 'AnsjerPush/file/apns_pem/accloud-dev.pem',
     }
 }
+
+XMPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': 'fXAdAwGDum3FKgQtAiW9hg=='
+}
+
+VIVOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID':'102227506',
+        'Key':'531bf5befece8840d31b654a166db91c',
+        'Secret':'9d6f19ef-5cdb-47ee-9433-d74c7d11fa50'
+    }
+}
+
+OPPOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': {
+        'Key': '882266a2000e4407990932be0b7043f5',
+        'Secret': '1037f29469c8416e87ab9cb8537c68ce'
+    }
+}
+
+MEIZUPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID': 151022,
+        'AppSecret': '890e94e09b7b4aa18278acce82e35c46',
+    }
+}
 APNS_MODE = 'dev'
 REDIS_ADDRESS = '127.0.0.1'

+ 1 - 1
AnsjerPush/local_settings.py → AnsjerPush/dev_config/local_settings.py

@@ -1,6 +1,6 @@
 import os
 
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 SECRET_KEY = '$2hf5g$a%_^kk0-l25l$!o5)yc=dvtnfpc8(+$rh4fq4twa_xx'
 DEBUG = True
 ALLOWED_HOSTS = ["*"]

+ 1 - 1
AnsjerPush/local_wsgi.py → AnsjerPush/dev_config/local_wsgi.py

@@ -11,6 +11,6 @@ import os
 
 from django.core.wsgi import get_wsgi_application
 
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.local_settings')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.dev_config.local_settings')
 
 application = get_wsgi_application()

+ 8 - 0
AnsjerPush/eur_config/__init__.py

@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : __init__.py.py
+@Time    : 2022/11/29 13:30
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""

+ 49 - 8
AnsjerPush/eur_formal_config.py → AnsjerPush/eur_config/eur_formal_config.py

@@ -1,9 +1,30 @@
+"""
+独立于config.py的配置文件
+"""
 import os
 import datetime
 
+# 配置信息
+CONFIG_INFO = 'eur'
+
+"""
+AWS相关
+"""
+# ======================================================================================================================
+# aws api key
+AWS_ARN_S3 = 'arn:aws:s3'
+REGION_NAME = 'us-east-1'
+ACCESS_KEY_ID = 'AKIA2E67UIMD45Y3HL53'
+SECRET_ACCESS_KEY = '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'
+
+# 存储桶
+PUSH_BUCKET = 'foreignpush'                                # 推送存储桶
+
+# redis节点
+REDIS_ADDRESS = 'push-redis-001.av1kep.0001.euw1.cache.amazonaws.com'
+
+APNS_MODE = 'prod'
 
-DEBUG_MODE = 'DEV'
-# MODE = 'PRO'
 # 阿里云发邮箱
 ALY_SES_ACCESS_NAME = 'message@dvema.com'
 ALY_SES_ACCESS_PAW = 'SMtp123456'
@@ -33,8 +54,6 @@ TX_PHONE_APP_KEY = '7705976ca6e85fe7b86d6bc2d11f7783'
 # 验证码超时时间
 AuthCode_Expire = 600
 
-# 根路径
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 # uid token key
 UID_TOKEN_KEY = 'c+565*j@%^'
 
@@ -43,8 +62,6 @@ OSS_STS_ACCESS_KEY = 'LTAIyMkGfEdogyL9'
 OSS_STS_ACCESS_SECRET = '71uIjpsqVOmF7DAITRyRuc259jHOjO'
 OSS_ROLE_ARN = 'acs:ram::1901342792446414:role/stsoss'
 
-SERVER_TYPE = 'Ansjer.eur_formal_settings'
-
 NGINX_RTMP_STAT = 'http://www.dvema.com/stat'
 SERVER_DOMAIN = 'http://www.dvema.com/'
 SERVER_DOMAIN_SSL = 'https://www.dvema.com/'
@@ -156,5 +173,29 @@ APNS_CONFIG = {
         'pem_path': 'AnsjerPush/file/apns_pem/commissionf.pem',
     }
 }
-APNS_MODE = 'prod'
-REDIS_ADDRESS = 'push-redis-001.av1kep.0001.euw1.cache.amazonaws.com'
+
+XMPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': 'fXAdAwGDum3FKgQtAiW9hg=='
+}
+
+VIVOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID':'102227506',
+        'Key':'531bf5befece8840d31b654a166db91c',
+        'Secret':'9d6f19ef-5cdb-47ee-9433-d74c7d11fa50'
+    }
+}
+
+MEIZUPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID': 151022,
+        'AppSecret': '890e94e09b7b4aa18278acce82e35c46',
+    }
+}
+
+OPPOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': {
+        'Key': '882266a2000e4407990932be0b7043f5',
+        'Secret': '1037f29469c8416e87ab9cb8537c68ce'
+    }
+}

+ 1 - 1
AnsjerPush/eur_formal_settings.py → AnsjerPush/eur_config/eur_formal_settings.py

@@ -1,6 +1,6 @@
 import os
 
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 SECRET_KEY = '$2hf5g$a%_^kk0-l25l$!o5)yc=dvtnfpc8(+$rh4fq4twa_xx'
 DEBUG = False
 ALLOWED_HOSTS = ["*"]

+ 1 - 1
AnsjerPush/eur_formal_wsgi.py → AnsjerPush/eur_config/eur_formal_wsgi.py

@@ -11,6 +11,6 @@ import os
 
 from django.core.wsgi import get_wsgi_application
 
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.eur_formal_settings')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.eur_config.eur_formal_settings')
 
 application = get_wsgi_application()

+ 0 - 3
AnsjerPush/file/alipay/alipay_private_2048.pem

@@ -1,3 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCkSf9JD/HprSulQiE6zHkcYX1f5nnDfaM5SCB8iVz/C7ClX+o4fj446kkq74BkB3k40S6RhttQRiRIkRinsR4/ZxgtwWOaUFCPrZebIqm5CBKp6UHCq8aiYFzBomrUWLLvtqfuwMiUh6J+0LLr1lVocQqu2aCCt/D62ac21bAD1WkDQzcDNVoeRAvsnvkyQj2ftN+BdR423/jzWabmNGATBvjk+dzuGMMl8jYDQcJ0nhpOXy3R0ytWMny1OT94VxVX7CwAJt0nzA9aw/f11wfYNqBt33/PuchzHb/Nond/OjIQ2Hg+vVeXnTjoK/FN4Qw5cM0apiHJU2ea7QxhzTZ7AgMBAAECggEBAKPrBOB0HU2mr46Crb8J9qjfiIVf4wuvVmU31toLABOlKUnHql1VcGW/NOO29SZY4pVS+SDQKhAs+9S/mJbwPJrMFS7J7sQv8EF61XI/IpJwQJbxGeDPHTAz7zhwAiD+owJT/7hS945iyjm3p11xi+PaLeuJTgI+3xyaf6FRkf4vnSf1oAicP1Ufg/eHr1BKPpL5BdtSVxtTKwOZpl4IkvvjBTvTVqzl/nHqOJXEew/H2EL48KDMhrZ9IfFCCUgMtowoIys/xcGmcuFbwo1+YIETDCEat4XnGBW/q81C/+l/KtP9scbT17AMBaH0dxLcJHPR1IVn8cVTw+1uctbRRUkCgYEA2wX2cpqGWnfTwb5gPchRr4hTzZx/EpDmNkOiGe1mgwoEH09ucRCAU54IOIWJnlUGO4rs4wwreIA/kO0DQkjM8Q5Xho/Wob/vjLemvgAx1ZaxspIp2fKDd/+Mk1ksXuK9RTASI46uAfQVx4S6vQGAOVnCQd3yHgyeplE9m21X+l0CgYEAwAZ1PqilcjjbhKnIBGbgPNuEGS3MrHanUAEYnb3AAxBjFLioAsgEYCsvE6l2MWz4ZSXLkG7qvNGqndUPY3sxltd9csrYRoTR7wiM1SJ/BVZGS7Bv9DeVlyxVg6WYgjKfDia4+UAIYyJjlB1n4+vmNScnTTzdpHa6CRC0WIAmVrcCgYBkJ+Df3VvM6PjTowv9lKoN++heoM7XHZ24eMsa0h3LlHv4GWmmmGYmVqDpv98DoVvdRJU46mrCitsGmeuglwvou6c4qHP8uXmAvsaoIgyn4GZYA1DSEn0eFaUiCGgq58KnbekNi4R07jo7Z63BGnuZPjt/xU+fgCPYaFQkzCasHQKBgQCoSsCkqq9dXOFe8mG/Id5y17x9SLtxhwf5wQwu6ycGSG1dsrxku6HCGvbGV6WjiaplE2vF6L/HvkW20A0NleGPHsruOUK1AyDuSmMLRJFoPwWavkZQFgr/k7Zai14O9YiaoWtJ30m6gGAfZmovwgZfHTJ/qdFimz9ORJqaWa3AHwKBgH2kjJx5D8c/IuNMQ3yErKj8a41u7852d04W65ZxlhaX3cFGpJVR4REA548U8K9Fy89Z7Xzf6ByI80e9clmy/I71vQfPEpxOGkI/1JnUjHdBDyk6Owqg4zOTUPlF2nUeJfi78G53W/gbqb6kKB2v0cxfk0QElvAPBZGsT9ptZbti
------END RSA PRIVATE KEY-----

+ 0 - 3
AnsjerPush/file/alipay/alipay_public_2048.pem

@@ -1,3 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6WSh//F8thrOf8Zm5CwIr9+HYy3+K4uRQtZoOolCRkbO47k++4da5/uBJz/vEoyX2/YwyqbdzIRbpwZlSyTKrRJpf5JwUE26dSVIjZ+2FOOUoRqf9MTDHhMEtvRvW2nSD3u4gDSz2vTuQPdiFzpJamuszR9ONdeIIYttoC//BR5TO3aDPHD1Q0D1WVnaBvWWnFUNaaW0BLjqLa3gtxWeUgMSePjusG28EX+aMomqSMReMRC87Hoan0TeQYU35Mgq6ZHGQ8RFBGAPfueHcvvtBLmiuL+UmJS5FyAq6QLCMSd74NnUAJwdQOi4e5vXeQX772DEyA/19RBXA4PESwJ0lwIDAQAB
------END PUBLIC KEY-----

+ 0 - 3
AnsjerPush/file/alipay/zosi_alipay_private_2048.pem

@@ -1,3 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCAtJ/+dopwzLOA6yq4yDiOIWRs1z1vJicOZF/O1xZOs+UN+B5yJvCsyyInEY9kWFRPoZoYjuZ5x8of23zELozZuebt1cbJOikRhMYQ8HiIZfPX+v9Uqve+j7qDcOAzJJwRz26eak8WA6+au6BVTyOKQa35oY3tbOQWN+Nu7zxqMjxrLmy/+af6WuQhhOY+EYjEMqMpc5iDRJzkSjPuWWzvD9PE5/AJiYsUs0lb9z1s2+O15eKp1nwEOJp3rOwfowin8MjR2BbGZ8SuugK3cU49X84sAsNXo0Anv/htV9Pp6QgAPs0b1E3F3NrHJifl/GAd/hpLby0tsoHKY8d7He+3AgMBAAECggEAEPe/IA0As15JS/W07Cd5TUOZub+mmcCt5XfWKa6xgi61Q+IyIVqA4Ebneb8W6GO8ucLUTeiI+gUc4JZPHI4Gg62+hBxq2JySTXzzn8gUG+dU/mRYxRDFGJpaPHnlFNj+ulpeewQI6R4TSJLz0EtepBuElCR4UZTJfSRhF37yuA03aqp2WVTDmvdLxY8ZIZcbovPA34VIs91Bs+s7UmgQKIT5YkMRUG7En5dgGB0Uce0JeoN35P9U+DrplX0YP51qwf7APQxEeKY/ifaDdGf69tm86wz188/HWOVUeBXKNbcPN/CtreLgb0zR8OLji/FKMIIW3JMrAlQEu3H6t3HIAQKBgQDDufBDF2fn+DmpyggNsKbS/L8M4n9Ak7W/5r3L9vh0RQ+fbqGMazURoqQOt6e+rjnPuoTB42Yk9XEUzZcXF65klq+kY+PVNKOuvVXMJpO+kb71aP6heJ5CQ0KHUsQ2zMxrxl0xdcj8WkBP5mLyz6gdNX8Ypfgq7Rn9YVddYtpPUQKBgQCoVxzyjbEhM5WmhhlUQ+2i/fz0hVmNPC52FN5xhI11VAqT4rS+EBr2LPaWJz0bsVCsaPshePNc/yNwUbWpg4VmdIc9NLHXOyq3mI9uLt4Nzwtm15UrVGuJA2c9CEIBRsrsX89tO1xvOPeHJ0GnX6rV0WByg/uGWyw4Ga3kIG1chwKBgC3t7S6hSHXL7yQjz73+rTcnrmGEqR3rS6QrrnA1lVobdq4QFQLRiZOI2fzGJiv90H0ppMum5CpaGl4qXIFDwl3CapcmBanbf2ma7o5IozT+GJSYPeCAYIrmI6+DwZ2mc3X7B5V6sV5eg2b8hk26YxXvntKW5+SS3R1zgT/d2aLxAoGAMq3XJyaFFuvCt9lTMs0oc5NnXe3vYdemXSI3PZGcTKA6dv8fTsDmfQpYSigR+45MLTuFrzQMgAjHkH7o49mWhZu7Qy/kAlnrqE4jkBZH/3w/gHySifLmVN9Ta0pbJKs9WCAYAFiIoWtTCbtFxq/EbI6WLKOTS4WTMGh6s10Xz20CgYEAmLF68mrO7Noq6w5jy7P2Gb6SvmriUW1jSwcpgq/jC/u83iMg2+++TYmGp01CCxZVPQ+pU8VOuMLoKlm4mn0Im4gxXU6GeGerVb/LO2WkYn7aFMNRV05bgg7nCM5Df1vV6UzYsU4uzM/VC8FFm7/bRTqKv0u3pv+sYGB0YrlZaHs=
------END RSA PRIVATE KEY-----

+ 0 - 3
AnsjerPush/file/alipay/zosi_alipay_public_2048.pem

@@ -1,3 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1usde7ybZRkDB0cx2a1QAgoVqwV+3HLOmB/f2lmqvWGhT9Kv2T09yu5+HuckQIr7EXIsyd+8gxv483xXkSdzVgm4UKD5QrVSaSs0s0iKOUAc2QOWF/rmzZ8/hDLHteNcYzyOYkr7ws7woCw0aN76oABzpBuch070mvTS7XNvV64YHtBeDo0iAGRaLu7OuHOOUJwQqFGzgaTtZz+TGXNk6ZonfYTcWbTw5Pc4kG9PjweBPpcsJXoCB0Ul9t/V66grhjnJ1TcYqzO0wY1Lr6J3p/0PtKVaG4Ijf6iGfmkOPBMy3lV9NPf/t20nuGLeBPkMMlGTytXVH5DkSB/6INytcQIDAQAB
------END PUBLIC KEY-----

BIN
AnsjerPush/file/font/simhei.ttf


+ 0 - 184
AnsjerPush/file/log type .txt

@@ -1,184 +0,0 @@
-typedef enum _log_type
-{
-	//系统控制(Control)
-	LOG_TYPE_SYSTEM_CTRL		= 0x00010000,
-	LOG_TYPE_BOOT,							//系统开机
-	LOG_TYPE_SHUTDOWN,						//系统关机
-	LOG_TYPE_REBOOT,						//系统重启
-	LOG_TYPE_FORMAT_SUCC,					//格式化磁盘成功
-	LOG_TYPE_FORMAT_FAIL,					//格式化磁盘失败
-	LOG_TYPE_UPGRADE_SUCC,					//升级成功
-	LOG_TYPE_UPGRADE_FAIL,					//升级失败
-	LOG_TYPE_CLEAR_ALARM,					//清除报警
-	LOG_TYPE_OPEN_ALARM,					//开启报警
-	LOG_TYPE_MANUAL_START,					//开启手动录像
-	LOG_TYPE_MANUAL_STOP,					//停止手动录像
-	LOG_TYPE_PTZ_ENTER,						//开始云台控制
-	LOG_TYPE_PTZ_CTRL,						//云台操作
-	LOG_TYPE_PTZ_EXIT,						//结束云台控制
-	LOG_TYPE_AUDIO_CH_CHANGE,				//改变现场音频通道
-	LOG_TYPE_VOLUME_ADJUST,					//调节音量
-	LOG_TYPE_MUTE_ENABLE,					//开启静音
-	LOG_TYPE_MUTE_DISENABLE,				//关闭静音
-	LOG_TYPE_DWELL_ENABLE,					//开启轮循
-	LOG_TYPE_DWELL_DISENABLE,				//关闭轮循
-	LOG_TYPE_LOG_IN,						//登录
-	LOG_TYPE_LOG_OFF,						//登出
-	LOG_TYPE_CHANGE_TIME,					//修改系统时间
-	LOG_TYPE_MANUAL_SNAP_SUCC,               //手动抓图成功
-	LOG_TYPE_MANUAL_SNAP_FAIL,               //手动抓图失败
-
-	//系统配置(Setup)
-	LOG_TYPE_CONFIG			= 0x00020000,
-	LOG_TYPE_CHGE_VIDEO_FORMAT,				//改变视频制式
-	LOG_TYPE_CHGE_VGA_RESOLUTION,			//改变显示器分辨率
-	LOG_TYPE_CHGE_LANGUAGE,					//调整语言
-	LOG_TYPE_CHGE_NET_USER_NUM,				//调整网络用户数目
-	LOG_TYPE_CHGE_TIME_ZONE,				//调整时区
-	LOG_TYPE_NTP_MANUAL,					//手动网络校时
-	LOG_TYPE_NTP_ON,						//开启自动网络校时
-	LOG_TYPE_NTP_OFF,						//关闭自动网络校时
-	LOG_TYPE_CHGE_NTP_SERVER,				//修改网络时间服务器地址
-	LOG_TYPE_CHGE_DST,						//调整夏令时设置
-	LOG_TYPE_PASSWD_ON,						//开启操作密码
-	LOG_TYPE_PASSWD_OFF,					//关闭操作密码
-
-	LOG_TYPE_CHGE_CAM_NAME,					//调整通道名称
-	LOG_TYPE_MODIFY_COLOR,					//调整图像色彩
-	LOG_TYPE_CHGE_HOST_MONITOR,				//调整主监视器画面设置
-	LOG_TYPE_CHGE_SPOT,						//调整辅助输出画面设置
-	LOG_TYPE_CHGE_OSD,						//调整字符叠加设置
-
-	LOG_TYPE_CHGE_LOCAL_ENCODE,				//调整录像流编码参数
-	LOG_TYPE_CHGE_REC_VIDEO_SWITCH,			//调整录像开关设置
-	LOG_TYPE_CHGE_REC_AUDIO_SWITCH,			//调整录制音频开关设置
-	LOG_TYPE_CHGE_REC_REDU_SWITCH,			//调整冗余录像开关设置
-	LOG_TYPE_CHGE_REC_PRE_TIME,				//调整景前录像时间
-	LOG_TYPE_CHGE_REC_POST_TIME,			//调整景后录像时间
-	LOG_TYPE_CHGE_REC_HOLD_TIME,			//调整录像数据过期时间
-
-	LOG_TYPE_CHGE_SCH_SCHEDULE,				//调整定时录像计划
-	LOG_TYPE_CHGE_SCH_MOTION,				//调整移动侦测录像计划
-	LOG_TYPE_CHGE_SCH_ALARM,				//调整报警录像计划
-
-	LOG_TYPE_CHGE_SENSOR_SWITCH,			//调整报警输入开关设置
-	LOG_TYPE_CHGE_SENSOR_TYPE,				//调整报警输入设备类型
-	LOG_TYPE_CHGE_SENSOR_TRIGGER,			//调整报警输入处理方式设置
-	LOG_TYPE_CHGE_SENSOR_SCH,				//调整报警输入侦测计划
-
-	LOG_TYPE_CHGE_MOTION_SWITCH,			//调整移动侦测开关设置
-	LOG_TYPE_CHGE_MOTION_SENS,				//调整移动侦测灵敏度
-	LOG_TYPE_CHGE_MOTION_AREA,				//调整移动侦测区域设置
-	LOG_TYPE_CHGE_MOTION_TRIGGER,			//调整移动侦测处理方式
-	LOG_TYPE_CHGE_MOTION_SCH,				//调整移动侦测计划
-
-	LOG_TYPE_CHGE_VL_TRIGGER,				//调整视频丢失处理方式设置
-
-	LOG_TYPE_CHGE_RELAY_SWITCH,				//调整报警输出开关设置
-	LOG_TYPE_CHGE_RELAY_SCH,				//调整报警输出计划
-
-	LOG_TYPE_BUZZER_ON,						//开启声音报警设备
-	LOG_TYPE_BUZZER_OFF,					//关闭声音报警设备
-	LOG_TYPE_CHGE_BUZZER_SCH,				//调整声音报警计划
-
-	LOG_TYPE_CHGE_HTTP_PORT,				//修改HTTP服务器端口
-	LOG_TYPE_CHGE_SER_PORT,					//修改网络服务器端口
-	LOG_TYPE_CHGE_IP,						//设置网络地址
-	LOG_TYPE_DHCP_SUCC,						//自动获取网络地址成功
-	LOG_TYPE_DHCP_FAIL,						//自动获取网络地址失败
-	LOG_TYPE_CHGE_PPPOE,					//设置PPPoE
-	LOG_TYPE_CHGE_DDNS,						//设置DDNS
-	LOG_TYPE_NET_STREAM_CFG,				//调整网络流编码设置
-
-	LOG_TYPE_CHGE_SERIAL,					//调整云台串口设置
-	LOG_TYPE_PRESET_MODIFY,					//调整预置点
-	LOG_TYPE_CRUISE_MODIFY,					//调整巡航线
-	LOG_TYPE_TRACK_MODIFY,					//调整轨迹
-
-	LOG_TYPE_USER_ADD,						//增加用户
-	LOG_TYPE_USER_MODIFY,					//调整用户权限
-	LOG_TYPE_USER_DELETE,					//删除用户
-	LOG_TYPE_CHANGE_PASSWD,					//修改用户密码
-
-	LOG_TYPE_LOAD_DEFAULT,					//恢复默认配置
-	LOG_TYPE_IMPORT_CONFIG,					//导入配置
-	LOG_TYPE_EXPORT_CONFIG,					//导出配置
-
-	LOG_TYPE_CHGE_IMAGE_MASK,				//图像遮挡
-	LOG_TYPE_RECYCLE_REC_ON,				//开启循环录像
-	LOG_TYPE_RECYCLE_REC_OFF,				//关闭循环录像
-	LOG_TYPE_CHGE_DISK_ALARM,				//调整磁盘报警空间
-
-	LOG_TYPE_CHGE_SEND_EMAIL,				//设置Email 发送人信息
-	LOG_TYPE_CHGE_RECV_EMAIL,				//设置Email 接收人信息
-	LOG_TYPE_CHGE_SNAP_SETTING,             //调整抓图配置
-
-	//yqf add 2017.12.22
-	LOG_TYPE_CHGE_SCH_PIR,					//调整人体红外感应录像计划
-	LOG_TYPE_CHGE_PIR_SWITCH,				//调整人体红外感应开关设置
-	LOG_TYPE_CHGE_PIR_TRIGGER,				//调整人体红外感应处理方式
-	LOG_TYPE_CHGE_PIR_SCH,					//调整人体红外感应计划
-
-	//录像回放(Playback)
-	LOG_TYPE_PLAYBACK		= 0x00040000,
-	LOG_TYPE_PLAYBACK_PLAY,					//播放
-	LOG_TYPE_PLAYBACK_PAUSE,				//暂停
-	LOG_TYPE_PLAYBACK_RESUME,				//恢复播放
-	LOG_TYPE_PLAYBACK_FF,					//快进
-	LOG_TYPE_PLAYBACK_REW,					//快退
-	LOG_TYPE_PLAYBACK_STOP,					//停止
-	LOG_TYPE_PLAYBACK_NEXT_SECTION,			//下一段
-	LOG_TYPE_PLAYBACK_PREV_SECTION,			//上一段
-
-	//数据备份(Backup)
-	LOG_TYPE_BACKUP			= 0x00080000,
-	LOG_TYPE_BACKUP_START,					//开始备份
-	LOG_TYPE_BACKUP_COMPLETE,				//备份完成
-	LOG_TYPE_BACKUP_CANCEL,					//放弃备份
-	LOG_TYPE_BACKUP_FAIL,					//备份失败
-
-	//录像检索(Search)
-	LOG_TYPE_SEARCH			= 0x00100000,
-	LOG_TYPE_SEARCH_TIME,					//按时间检索
-	LOG_TYPE_SEARCH_EVENT,					//按事件检索
-	LOG_TYPE_SEARCH_FILE_MAN,				//文件管理
-	LOG_TYPE_DELETE_FILE,					//删除文件
-	LOG_TYPE_LOCK_FILE,						//锁定文件
-	LOG_TYPE_UNLOCK_FILE,					//解锁文件
-	LOG_TYPE_DELETE_PICTURE,                //删除图片
-	LOG_TYPE_LOCK_PICTURE,                  //锁定图片
-	LOG_TYPE_UNLOCK_PICTURE,                //解锁图片
-
-
-	//查看信息(View information)
-	LOG_TYPE_VIEW_INFO		= 0x00200000,
-	LOG_TYPE_VIEW_SYSTEM,					//查看系统信息
-	LOG_TYPE_VIEW_EVENT,					//查看事件
-	LOG_TYPE_VIEW_LOG,						//查看日志
-	LOG_TYPE_VIEW_NETWORK,					//查看网络状态
-	LOG_TYPE_VIEW_ONLINE_USER,				//查看在线用户
-	LOG_TYPE_VIEW_EXPORT_LOG,				//导出日志
-	LOG_TYPE_VIEW_EXPORT_EVENT,				//导出事件
-
-	//异常信息(Error)
-	LOG_TYPE_ERROR_INFO		= 0x00400000,
-	LOG_TYPE_IP_CONFLICT,					//网络地址冲突
-	LOG_TYPE_NETWORK_ERR,					//网络异常
-	LOG_TYPE_DDNS_ERR,						//DDNS错误
-	LOG_TYPE_DISK_IO_ERR,					//磁盘读写错误
-	LOG_TYPE_UNKNOWN_OFF,					//异常断电
-	LOG_TYPE_UNKNOWN_ERR,					//未知错误
-	LOG_TYPE_ERR_9A9A9A9A,
-	LOG_TYPE_ERR_9A000001,
-	LOG_TYPE_ERR_9A000002,
-	LOG_TYE_DISK_WARNING,                  //磁盘衰减报警
-	LOG_TYE_DISK_DISCONNECT,                  //磁盘掉线报警
-	//LOG_TYE_DISK_NOTFIND,					//开机无硬盘
-
-    LOG_TYPE_EVENT            = 0x00800000,   //事件信息
-    LOG_TYPE_EVENT_MOTION     = 0x00800001,
-    LOG_TYPE_EVENT_SENSOR     = 0x00800002,
-    LOG_TYPE_EVENT_VIDEO_LOSS = 0x00800004,
-    LOG_TYPE_EVENT_COVER      = 0x00800008,
-    LOG_TYPE_EVENT_PIR		  = 0x00800010,   //yqf add 2017.12.26
-}LOG_TYPE;

+ 0 - 69
AnsjerPush/file/后台管理系统说明.txt

@@ -1,69 +0,0 @@
-一.修改记录
-创建时间: 2017/10/25 版本: 1.0
-
-二.项目说明
-	1.架构概述
-		1).现有模块 :  用户模块(包括用户设备共享模块)  设备信息模块 OTA模块 Token验证模块
-		2).整体说明 :                                  OTA模块
-		                                                 ↗ 
-														 
-			用户注册 -> 用户登录  -> Token验证模块 
-
-				 ↓		          ↓                     ↘
-			 用户模块       用户模块             设备信息模块
-		3).各个模块明细说明
-			1. 用户模块: 
-				用户信息处理模块:用户基本操作
-					用户注册:包括获取验证码,然后通过验证注册用户
-					用户登录:返回Token信息给后续验证用户安全性
-					用户信息完善:修改完善用户个人信息
-					用户修改密码:修改用户密码
-					用户重置密码:重置密码(使用邮箱进行重置)
-					显示用户信息:显示本用户当前信息
-					显示所有用户信息:只有管理员与超级管理员可以调用,显示所有的用户信息
-					用户启用、禁用:只有管理员与超级管理员可以调用,启用、禁用设备用户
-				
-				用户设备共享模块:共享用户设备
-					搜索用户:通过用户的信息搜索用户自身另外的账号
-					共享用户设备:主用户把设备共享给搜索到用户(可以共享一台设备,也可以全部等设备)
-					取消用户设备:主用户取消之前共享给其他账号的设备(可以取消共享一台设备,也可以全部等设备)
-					
-			2.Token验证模块:
-				token获取:在登录时调用获取Token
-				token验证:解密access token,验证有效期,如果有效可以继续访问接口,无效返回错误信息
-				token更新:解密refresh token,验证有效性,如果有效并且没有过期就更新access token,然后返回access token,或者返回错误信息
-				token错误信息:通过上面三个接口返回的错误码,解析出来token错误信息,并返回提示用户
-				
-			3.设备信息模块
-				添加设备信息:添加新设备信息
-				查询设备信息:查询本账户名下所有设备信息
-				修改设备信息:修改本账户名下的相应设备信息
-				删除设备信息:删除本账户名下相应设备信息
-				显示所有用户设备信息:只有管理员与超级管理员可以调用,显示所有的用户名下所有设备信息
-			
-			4.OTA模块
-				升级文件上传:上传升级文件(根据文件名称保存到相应的文件夹下)
-				添加版本信息:把上传的升级文件信息更新到数据库表里面
-				获取最新版本信息:通过输入要查询的设备信息,返回查询到该设备最新版本
-				获取最新版本url链接:通过输入要查询的设备信息,返回查询到该设备升级文件的下载链接
-				下载升级文件:通过获取的url下载升级文件
-
-			5.批量处理模块
-				设备信息批量导入:2种内容格式txt格式, 2种调用Excel文件导入方式
-				
-			6.权限模块
-				模型设计:用户、角色、权限,用户 → 多对多 ← 角色, 用户 → 多对多 ← 权限,角色 → 多对多 ← 权限
-			7.日志模块
-			    中间件middle入口进行统计入库,利用nginx自带module进行统计(更精确)
-			8.APP版本信息模块
-			    对应app版本更新,检测版本详细信息
-			9.语言包模块
-			    所有响应信息增加中英适配
-			10.
-
-
-django常用命令
-python3 mangage.py makemigrations
-python3 manage.py migrate --fake
-python3 manage.py sqlmigrate
-python3 manage.py runserver 0.0.0.0:8222

BIN
AnsjerPush/file/表文档.doc


+ 8 - 0
AnsjerPush/test_config/__init__.py

@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : __init__.py.py
+@Time    : 2022/11/29 10:35
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""

+ 0 - 0
AnsjerPush/test.py → AnsjerPush/test_config/test.py


+ 48 - 32
AnsjerPush/test_config.py → AnsjerPush/test_config/test_config.py

@@ -1,25 +1,29 @@
-#!/usr/bin/env python3  
-# -*- coding: utf-8 -*-  
 """
-@Copyright (C) ansjer cop Video Technology Co.,Ltd.All rights reserved.
-@AUTHOR: ASJRD018
-@NAME: Ansjer
-@software: PyCharm
-@DATE: 2018/7/2 14:06
-@Version: python3.6
-@MODIFY DECORD:ansjer dev
-@file: Conf.py
-@Contact: chanjunkai@163.com
+独立于config.py的配置文件
 """
-# 主要静态变量配置文件
-import datetime, os
+import os
+import datetime
 
+# 配置信息
+CONFIG_INFO = 'test'
 
-# MODE = 'PRO'
-# 阿里云发邮箱
-ALY_SES_ACCESS_NAME = 'message@dvema.com'
-ALY_SES_ACCESS_PAW = 'SMtp123456'
-ALY_SES_ACCESS_REPLYTO = '***'
+"""
+AWS相关
+"""
+# ======================================================================================================================
+# aws api key
+AWS_ARN_S3 = 'arn:aws:s3'
+REGION_NAME = 'us-east-1'
+ACCESS_KEY_ID = 'AKIA2E67UIMD45Y3HL53'
+SECRET_ACCESS_KEY = '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'
+
+# 存储桶
+PUSH_BUCKET = 'foreignpush'                                # 推送存储桶
+
+# redis节点
+REDIS_ADDRESS = '127.0.0.1'
+
+APNS_MODE = 'dev'
 
 # 发送邮件邮箱
 SES_COMPANY_EMAIL = 'user_server@nsst.com'
@@ -27,14 +31,11 @@ AWS_SES_ACCESS_ID = 'AKIAJKPU23EU5QWHFPKQ'
 AWS_SES_ACCESS_SECRET = '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'
 AWS_SES_ACCESS_REGION = 'us-east-1'
 AWS_BUCKET = 'ansjertest'
-# 设定离线时间为5分钟
-OFF_LINE_TIME_DELTA = 5
 
 # token的secret
 OAUTH_ACCESS_TOKEN_SECRET = 'a+jbgnw%@1%zy^=@dn62%'
 OAUTH_REFRESH_TOKEN_SECRET = 'r+jbgnw%@1%zy^=@dn62%'
 # access_token超时
-# OAUTH_ACCESS_TOKEN_TIME = datetime.timedelta(hours=1)
 OAUTH_ACCESS_TOKEN_TIME = datetime.timedelta(days=30)
 # refresh_token超时
 OAUTH_REFRESH_TOKEN_TIME = datetime.timedelta(days=30)
@@ -45,8 +46,6 @@ TX_PHONE_APP_KEY = '7705976ca6e85fe7b86d6bc2d11f7783'
 # 验证码超时时间
 AuthCode_Expire = 600
 
-# 根路径
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 # uid token key
 UID_TOKEN_KEY = 'c+565*j@%^'
 
@@ -55,18 +54,11 @@ OSS_STS_ACCESS_KEY = 'LTAIyMkGfEdogyL9'
 OSS_STS_ACCESS_SECRET = '71uIjpsqVOmF7DAITRyRuc259jHOjO'
 OSS_ROLE_ARN = 'acs:ram::1901342792446414:role/stsoss'
 
-SERVER_TYPE = 'Ansjer.test_settings'
-
 NGINX_RTMP_STAT = 'http://www.dvema.com/stat'
 SERVER_DOMAIN = 'http://www.dvema.com/'
 SERVER_DOMAIN_SSL = 'https://www.dvema.com/'
 DOMAIN_HOST = 'www.dvema.com'
 SERVER_HOST = 'localhost'
-PAYPAL_CRD = {
-    "mode": "live",  # sandbox or live
-    "client_id": "AdSRd6WBn-qLl9OiQHQuNYTDFSx0ZX0RUttqa58au8bPzoGYQUrt8bc6591RmH8_pEAIPijdvVYSVXyI",
-    "client_secret": "ENT-J08N3Fw0B0uAokg4RukljAwO9hFHPf8whE6-Dwd8oBWJO8AWMgpdTKpfB1pOy89t4bsFEzMWDowm"
-}
 DETECT_PUSH_DOMAIN = 'http://push.dvema.com/'
 
 
@@ -158,5 +150,29 @@ APNS_CONFIG = {
         'pem_path': 'AnsjerPush/file/apns_pem/commissionf-dev.pem',
     }
 }
-APNS_MODE = 'dev'
-REDIS_ADDRESS = '127.0.0.1'
+
+XMPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': 'fXAdAwGDum3FKgQtAiW9hg=='
+}
+
+VIVOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID':'102227506',
+        'Key':'531bf5befece8840d31b654a166db91c',
+        'Secret':'9d6f19ef-5cdb-47ee-9433-d74c7d11fa50'
+    }
+}
+
+OPPOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': {
+        'Key': '882266a2000e4407990932be0b7043f5',
+        'Secret': '1037f29469c8416e87ab9cb8537c68ce'
+    }
+}
+
+MEIZUPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID': 151022,
+        'AppSecret': '890e94e09b7b4aa18278acce82e35c46',
+    }
+}

+ 2 - 1
AnsjerPush/test_settings.py → AnsjerPush/test_config/test_settings.py

@@ -1,6 +1,7 @@
 import os
 
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+print()
 SECRET_KEY = '$2hf5g$a%_^kk0-l25l$!o5)yc=dvtnfpc8(+$rh4fq4twa_xx'
 DEBUG = True
 ALLOWED_HOSTS = ["*"]

+ 1 - 1
AnsjerPush/test_wsgi.py → AnsjerPush/test_config/test_wsgi.py

@@ -11,6 +11,6 @@ import os
 
 from django.core.wsgi import get_wsgi_application
 
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.test_settings')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.test_config.test_settings')
 
 application = get_wsgi_application()

+ 6 - 6
AnsjerPush/urls.py

@@ -1,18 +1,18 @@
-from django.conf.urls import url
-from django.urls import path
+from django.urls import path, re_path
 
 from Controller import DetectController, ShadowController, DetectControllerV2, AiController, gatewayController, \
     PowerWarningController
 from Controller.ComboCron import ComboCronPushController
 
 urlpatterns = [
-    path('deviceShadow/update', ShadowController.update_device_shadow),
     path('deviceShadow/generateUTK', ShadowController.generate_utk),
+    path('deviceShadow/update', ShadowController.update_device_shadow),
+
     path('notify/push', DetectController.NotificationView.as_view()),
     path('notifyV2/push', DetectControllerV2.NotificationV2View.as_view()),
     path('notifyV2/powerWarningPush', PowerWarningController.PowerWarningView.as_view()),
 
-    url(r'^AiService/(?P<operation>.*)$', AiController.AiView.as_view()),
-    url(r'^gatewayService/(?P<operation>.*)$', gatewayController.GatewayView.as_view()),
-    url('unicom/device/combo/(?P<operation>.*)$', ComboCronPushController.ComboCronPushView.as_view())
+    re_path(r'^AiService/(?P<operation>.*)$', AiController.AiView.as_view()),
+    re_path(r'^gatewayService/(?P<operation>.*)$', gatewayController.GatewayView.as_view()),
+    re_path('unicom/device/combo/(?P<operation>.*)$', ComboCronPushController.ComboCronPushView.as_view())
 ]

+ 8 - 0
AnsjerPush/us_config/__init__.py

@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : __init__.py.py
+@Time    : 2022/11/29 10:41
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""

+ 49 - 22
AnsjerPush/formal_config.py → AnsjerPush/us_config/formal_config.py

@@ -1,22 +1,30 @@
-#!/usr/bin/env python3  
-# -*- coding: utf-8 -*-  
 """
-@Copyright (C) ansjer cop Video Technology Co.,Ltd.All rights reserved.
-@AUTHOR: ASJRD018
-@NAME: Ansjer
-@software: PyCharm
-@DATE: 2018/7/2 14:06
-@Version: python3.6
-@MODIFY DECORD:ansjer dev
-@file: Conf.py
-@Contact: chanjunkai@163.com
+独立于config.py的配置文件
 """
-# 主要静态变量配置文件
-import datetime, os
+import os
+import datetime
 
+# 配置信息
+CONFIG_INFO = 'us'
+
+"""
+AWS相关
+"""
+# ======================================================================================================================
+# aws api key
+AWS_ARN_S3 = 'arn:aws:s3'
+REGION_NAME = 'us-east-1'
+ACCESS_KEY_ID = 'AKIA2E67UIMD45Y3HL53'
+SECRET_ACCESS_KEY = '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'
+
+# 存储桶
+PUSH_BUCKET = 'foreignpush'                                # 推送存储桶
+
+# redis节点
+REDIS_ADDRESS = 'pushredis.5tgle2.0001.usw1.cache.amazonaws.com'
+
+APNS_MODE = 'prod'
 
-DEBUG_MODE = 'DEV'
-# MODE = 'PRO'
 # 阿里云发邮箱
 ALY_SES_ACCESS_NAME = 'message@dvema.com'
 ALY_SES_ACCESS_PAW = 'SMtp123456'
@@ -46,8 +54,6 @@ TX_PHONE_APP_KEY = '7705976ca6e85fe7b86d6bc2d11f7783'
 # 验证码超时时间
 AuthCode_Expire = 600
 
-# 根路径
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 # uid token key
 UID_TOKEN_KEY = 'c+565*j@%^'
 
@@ -56,8 +62,6 @@ OSS_STS_ACCESS_KEY = 'LTAIyMkGfEdogyL9'
 OSS_STS_ACCESS_SECRET = '71uIjpsqVOmF7DAITRyRuc259jHOjO'
 OSS_ROLE_ARN = 'acs:ram::1901342792446414:role/stsoss'
 
-SERVER_TYPE = 'Ansjer.formal_settings'
-
 NGINX_RTMP_STAT = 'http://www.dvema.com/stat'
 SERVER_DOMAIN = 'http://www.dvema.com/'
 SERVER_DOMAIN_SSL = 'https://www.dvema.com/'
@@ -169,6 +173,29 @@ APNS_CONFIG = {
         'pem_path': 'AnsjerPush/file/apns_pem/commissionf.pem',
     }
 }
-APNS_MODE = 'prod'
-REDIS_ADDRESS = 'pushredis.5tgle2.0001.usw1.cache.amazonaws.com'
-# REDIS_ADDRESS = '192.168.136.45'
+
+XMPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': 'fXAdAwGDum3FKgQtAiW9hg=='
+}
+
+VIVOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID':'102227506',
+        'Key':'531bf5befece8840d31b654a166db91c',
+        'Secret':'9d6f19ef-5cdb-47ee-9433-d74c7d11fa50'
+    }
+}
+
+OPPOPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab': {
+        'Key': '882266a2000e4407990932be0b7043f5',
+        'Secret': '1037f29469c8416e87ab9cb8537c68ce'
+    }
+}
+
+MEIZUPUSH_CONFIG = {
+    'com.ansjer.zccloud_ab':{
+        'ID': 151022,
+        'AppSecret': '890e94e09b7b4aa18278acce82e35c46',
+    }
+}

+ 1 - 1
AnsjerPush/formal_settings.py → AnsjerPush/us_config/formal_settings.py

@@ -1,6 +1,6 @@
 import os
 
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 SECRET_KEY = '$2hf5g$a%_^kk0-l25l$!o5)yc=dvtnfpc8(+$rh4fq4twa_xx'
 DEBUG = False
 ALLOWED_HOSTS = ["*"]

+ 1 - 1
AnsjerPush/formal_wsgi.py → AnsjerPush/us_config/formal_wsgi.py

@@ -11,6 +11,6 @@ import os
 
 from django.core.wsgi import get_wsgi_application
 
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.formal_settings')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.us_config.formal_settings')
 
 application = get_wsgi_application()

+ 1 - 83
Controller/AiController.py

@@ -19,7 +19,7 @@ import jpush
 from boto3.session import Session
 from django.views.generic.base import View
 from pyfcm import FCMNotification
-from AnsjerPush.config import SERVER_TYPE, AI_IDENTIFICATION_TAGS_DICT
+from AnsjerPush.config import SERVER_TYPE
 from AnsjerPush.config import AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, APNS_MODE, APNS_CONFIG, BASE_DIR, \
     JPUSH_CONFIG, FCM_CONFIG
 from Model.models import UidPushModel, AiService
@@ -279,88 +279,6 @@ class AiView(View):
                     os.remove(c_path)
             os.rmdir(path)
 
-    ## 检查是否有符合条件的标签,并且返回标签坐标位置信息
-    def labelsCoords(self, user_detect_group, rekognition_res, image_size):
-        logger = logging.getLogger('info')
-        labels = rekognition_res['Labels']
-        label_name = []
-        label_list = []
-        logger.info('--------识别到的标签-------')
-        logger.info(labels)
-        all_labels_type = {
-            '1': ['Person', 'Human'],  # 人
-            '2': ['Pet', 'Dog', 'Canine', 'Animal', 'Puppy', 'Cat'],  # 动物
-            '3': ['Vehicle', 'Car', 'Transportation', 'Automobile', 'Bus'],  # 车
-            '4': ['Package', 'Carton', 'Cardboard', 'Package Delivery']  # 包裹
-        }
-        # 找出识别的所有标签
-        for label in labels:
-            label_name.append(label['Name'])
-            for Parents in label['Parents']:
-                label_name.append(Parents['Name'])
-
-        logger.info('标签名------')
-        logger.info(label_name)
-        # 删除用户没有选择的ai识别类型, 并且得出最终识别结果
-        user_detect_list = user_detect_group.split(',')
-        user_detect_list = [i.strip() for i in user_detect_list]
-        conform_label_list = []
-        conform_user_d_group = set()
-        for key, label_type_val in all_labels_type.items():
-            if key in user_detect_list:
-                for label in label_type_val:
-                    if label in label_name:
-                        conform_user_d_group.add(key)
-                        conform_label_list.append(label)
-        # 找出标签边框线位置信息
-        boundingBoxList = []
-        for label in labels:
-            if label['Name'] in conform_label_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']
-        new_bounding_box_dict = {}
-        new_bounding_box_dict['file_0'] = []
-        new_bounding_box_dict['file_1'] = []
-        new_bounding_box_dict['file_2'] = []
-        # new_bounding_box_dict['file_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
-                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['Left'] = val['Left']
-                    new_bounding_box_dict["file_{i}".format(i=i)].append(boxDict)
-        # exit(new_bounding_box_list)
-        conform_user_d_group = list(conform_user_d_group)
-        if len(conform_user_d_group) > 1:
-            conform_user_d_group.sort()
-            # 集成识别标签
-            for label_key in conform_user_d_group:
-                label_list.append(AI_IDENTIFICATION_TAGS_DICT[label_key])
-            eventType = ''.join(conform_user_d_group)  # 组合类型
-        else:
-            label_list.append(AI_IDENTIFICATION_TAGS_DICT[conform_user_d_group[0]])
-            eventType = conform_user_d_group[0]
-
-        logger.info('------conform_user_d_group------ {}'.format(conform_user_d_group))
-        logger.info('------label_list------ {}'.format(label_list))
-
-        return {'eventType': eventType, 'label_list': label_list,
-                'new_bounding_box_dict': new_bounding_box_dict}
-
     def upload_s3(self, file_dict, dir_path):
         try:
             if SERVER_TYPE == "Ansjer.formal_settings" or SERVER_TYPE == 'Ansjer.eur_formal_settings':

+ 22 - 6
Controller/ComboCron/ComboCronPushController.py

@@ -16,7 +16,8 @@ from django.views import View
 from Model.models import UnicomComboOrderInfo, UnicomDeviceInfo, GatewayPush, SysMsgModel, UnicomFlowPush, Device_User
 from Object.AliyunSmsObject import AliyunSmsObject
 from Object.ResponseObject import ResponseObject
-from Service.GatewayService import GatewayPushService
+from Service.HuaweiPushService.HuaweiPushService import HuaweiPushObject
+from Service.PushService import PushObject
 
 
 class ComboCronPushView(View):
@@ -102,7 +103,7 @@ class ComboCronPushView(View):
                 app_bundle_id = push_vo['app_bundle_id']
 
                 # 获取推送所需数据
-                msg_title = GatewayPushService.get_msg_title(app_bundle_id, nickname)
+                msg_title = PushObject.get_msg_title(nickname)
                 if lang == 'cn':
                     sys_msg_text = "温馨提示:尊敬的客户,您" + nickname + "设备4G流量套餐将在" + time.strftime("%Y-%m-%d", time.localtime(
                         item['expire_time'])) + "到期"
@@ -183,7 +184,7 @@ class ComboCronPushView(View):
                     app_bundle_id = push_vo['app_bundle_id']
 
                     # 获取推送所需数据
-                    msg_title = GatewayPushService.get_msg_title(app_bundle_id, item.serial_no)
+                    msg_title = PushObject.get_msg_title(item.serial_no)
                     sys_msg_text = sys_msg if msg else cls.get_msg_text(item.serial_no, lang, total, usage, usable)
                     kwargs['app_bundle_id'] = app_bundle_id
                     kwargs['token_val'] = token_val
@@ -222,13 +223,28 @@ class ComboCronPushView(View):
         try:
             # ios apns
             if push_type == 0:
-                GatewayPushService.ios_apns_push(**kwargs)
+                PushObject.ios_apns_push(**kwargs)
             # android gcm
             elif push_type == 1:
-                GatewayPushService.android_fcm_push(**kwargs)
+                PushObject.android_fcm_push(**kwargs)
             # android 极光推送
             elif push_type == 2:
-                GatewayPushService.android_jpush(**kwargs)
+                PushObject.android_jpush(**kwargs)
+            elif push_type == 3:
+                huawei_push_object = HuaweiPushObject()
+                huawei_push_object.send_push_notify_message(**kwargs)
+            # android 小米推送
+            elif push_type == 4:
+                PushObject.android_xmpush(**kwargs)
+            # android vivo推送
+            elif push_type == 5:
+                PushObject.android_vivopush(**kwargs)
+            # android oppo推送
+            elif push_type == 6:
+                PushObject.android_oppopush(**kwargs)
+            # android 魅族推送
+            elif push_type == 7:
+                PushObject.android_meizupush(**kwargs)
             return True
         except Exception as e:
             logger.info('流量预警推送异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))

+ 178 - 367
Controller/DetectController.py

@@ -1,16 +1,4 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-@Copyright (C) ansjer cop Video Technology Co.,Ltd.All rights reserved.
-@AUTHOR: ASJRD018
-@NAME: AnsjerFormal
-@software: PyCharm
-@DATE: 2019/1/14 15:57
-@Version: python3.6
-@MODIFY DECORD:ansjer dev
-@file: DetectController.py
-@Contact: chanjunkai@163.com
-"""
+import json
 import logging
 import os
 import time
@@ -18,6 +6,7 @@ import time
 import apns2
 import jpush as jpush
 import oss2
+from django.db import transaction
 from django.http import JsonResponse
 from django.views.generic.base import View
 from pyfcm import FCMNotification
@@ -31,6 +20,7 @@ from Object.RedisObject import RedisObject
 from Object.UidTokenObject import UidTokenObject
 from Object.utils import LocalDateTimeUtil
 from Service.CommonService import CommonService
+from Service.DevicePushService import DevicePushService
 from Service.EquipmentInfoService import EquipmentInfoService
 
 
@@ -46,6 +36,15 @@ class NotificationView(View):
         return self.validation(request.POST)
 
     def validation(self, request_dict):
+        """
+        设备触发报警消息推送
+        @param request_dict:uidToken 加密uid
+        @param request_dict:etk 加密uid
+        @param request_dict:channel 设备通道号
+        @param request_dict:n_time 设备触发报警时间
+        @param request_dict:event_type 设备事件类型
+        @param request_dict:is_st 文件类型(0:无,1:图片,2:视频)
+        """
         logger = logging.getLogger('info')
         logger.info("旧移动侦测接口参数:{}".format(request_dict))
         uidToken = request_dict.get('uidToken', None)
@@ -56,365 +55,177 @@ class NotificationView(View):
         is_st = request_dict.get('is_st', None)
         if not all([channel, n_time]):
             return JsonResponse(status=200, data={'code': 444, 'msg': 'error channel or n_time'})
-        if etk:
-            eto = ETkObject(etk)
-            uid = eto.uid
-            if len(uid) != 20:
-                return JsonResponse(status=200, data={'code': 404, 'msg': 'data is not exist'})
-        else:
-            utko = UidTokenObject(uidToken)
-            uid = utko.UID
-        logger.info("旧移动侦测接口的uid:{}".format(uid))
-        pkey = '{uid}_{channel}_{event_type}_ptl'.format(uid=uid, event_type=event_type, channel=channel)
-        ykey = '{uid}_redis_qs'.format(uid=uid)
-        is_sys_msg = self.is_sys_msg(int(event_type))
-        if is_sys_msg is True:
-            dkey = '{uid}_{channel}_{event_type}_flag'.format(uid=uid, event_type=event_type, channel=channel)
-        else:
-            dkey = '{uid}_{channel}_flag'.format(uid=uid, channel=channel)
-
-        redisObj = RedisObject(db=6)
-        have_ykey = redisObj.get_data(key=ykey)  # uid_set 数据库缓存
-        have_pkey = redisObj.get_data(key=pkey)  # 一分钟限制key
-        have_dkey = redisObj.get_data(key=dkey)  # 推送类型限制
-
-        # 一分钟外,推送开启状态
-        detect_med_type = 0  # 0推送旧机制 1存库不推送,2推送存库
-        # 暂时注销
-        if have_pkey:
-            res_data = {'code': 0, 'msg': 'Push it once a minute'}
-            return JsonResponse(status=200, data=res_data)
-
-        # 数据库读取数据
-        if have_ykey:
-            uid_push_list = eval(redisObj.get_data(key=ykey))
-        else:
-            # 从数据库查询出来
-            uid_push_qs = UidPushModel.objects.filter(uid_set__uid=uid, uid_set__detect_status=1). \
-                values('token_val', 'app_type', 'appBundleId', 'm_code',
-                       'push_type', 'userID_id', 'userID__NickName',
-                       'lang', 'm_code', 'tz', 'uid_set__nickname', 'uid_set__detect_interval', 'uid_set__detect_group',
-                       'uid_set__channel', 'uid_set__new_detect_interval')
-
-            uid_push_list = []
-            for qs in uid_push_qs:
-                uid_push_list.append(qs)
-            # 修改redis数据,并设置过期时间为10分钟
-            redisObj.set_data(key=ykey, val=str(uid_push_list), expire=600)
-            if not uid_push_list:
-                res_data = {'code': 404, 'msg': 'error !'}
-                return JsonResponse(status=200, data=res_data)
-
-        if not uid_push_list:
-            res_data = {'code': 0, 'msg': 'uid_push_list not exist'}
-            return JsonResponse(status=200, data=res_data)
-
-        nickname = uid_push_list[0]['uid_set__nickname']
-        detect_interval = uid_push_list[0]['uid_set__detect_interval']
-        detect_group = uid_push_list[0]['uid_set__detect_group']
-        now_time = int(time.time())
-        if not nickname:
-            nickname = uid
-
-        if detect_group is not None:
-            if have_dkey:
-                detect_med_type = 1  # 1为存库不推送
-            else:
-                detect_med_type = 2  # 为2的话,既推送,又存库
-                if SERVER_TYPE != 'Ansjer.cn_formal_settings':
-                    new_detect_interval = uid_push_list[0]['uid_set__new_detect_interval']
-                    detect_interval = new_detect_interval if new_detect_interval > 0 else detect_interval
-                    detect_interval = 60 if detect_interval < 60 else detect_interval
-                redisObj.set_data(key=dkey, val=1, expire=detect_interval - 5)
-                redisObj.set_data(key=pkey, val=1, expire=60)
-        logger.info('APP消息推送V1接口,是否进行APP推送:{},1为不推送,间隔:{}'.format(detect_med_type, detect_interval))
-        # 旧模式并且没有pkey,重新创建一个
-        if not detect_group and not have_pkey:
-            redisObj.set_data(key=pkey, val=1, expire=60)
-        auth = oss2.Auth(OSS_STS_ACCESS_KEY, OSS_STS_ACCESS_SECRET)
-        bucket = oss2.Bucket(auth, 'oss-cn-shenzhen.aliyuncs.com', 'apg')
-        kwag_args = {
-            'uid': uid,
-            'channel': channel,
-            'event_type': event_type,
-            'n_time': n_time,
-        }
-        sys_msg_list = []
-        userID_ids = []
-        do_apns_code = ''
-        do_fcm_code = ''
-        do_jpush_code = ''
-        new_device_info_list = []
-        local_date_time = ''
-        for up in uid_push_list:
-            push_type = up['push_type']
-            appBundleId = up['appBundleId']
-            token_val = up['token_val']
-            lang = up['lang']
-            tz = up['tz']
-            if tz is None or tz == '':
-                tz = 0
-            # 发送标题
-            msg_title = self.get_msg_title(appBundleId=appBundleId, nickname=nickname)
-            # 发送内容
-            msg_text = self.get_msg_text(channel=channel, n_time=n_time, lang=lang, tz=tz,
-                                         event_type=event_type)
-            kwag_args['appBundleId'] = appBundleId
-            kwag_args['token_val'] = token_val
-            kwag_args['msg_title'] = msg_title
-            kwag_args['msg_text'] = msg_text
-            logger.info('推送要的数据:')
-            logger.info(kwag_args)
-            logger.info(detect_med_type)
-            local_date_time = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang='cn')
-            logger.info('<<<<<旧的接口,根据时区计算后日期={},时区={}'.format(local_date_time, tz))
-            local_date_time = local_date_time[0:10]
-            logger.info('<<<<<旧的接口,日期={}'.format(local_date_time))
-
-            # 以下是存库
-            userID_id = up["userID_id"]
-            if userID_id not in userID_ids:
-
+        try:
+            with transaction.atomic():
+                uid = DevicePushService.decode_uid(etk, uidToken)  # 解密uid
+                if len(uid) != 20 and len(uid) != 14:
+                    return JsonResponse(status=200, data={'code': 404, 'msg': 'wrong uid'})
+                logger.info("旧移动侦测接口的uid:{}".format(uid))
+                pkey = '{uid}_{channel}_{event_type}_ptl'.format(uid=uid, event_type=event_type, channel=channel)
+                ykey = '{uid}_redis_qs'.format(uid=uid)
+                is_sys_msg = self.is_sys_msg(int(event_type))
+                if is_sys_msg is True:
+                    dkey = '{uid}_{channel}_{event_type}_flag'.format(uid=uid, event_type=event_type, channel=channel)
+                else:
+                    dkey = '{uid}_{channel}_flag'.format(uid=uid, channel=channel)
+
+                redisObj = RedisObject(db=6)
+                have_ykey = redisObj.get_data(key=ykey)  # uid_set 数据库缓存
+                have_pkey = redisObj.get_data(key=pkey)  # 一分钟限制key
+                have_dkey = redisObj.get_data(key=dkey)  # 推送类型限制
+
+                # 一分钟外,推送开启状态
+                detect_med_type = 0  # 0推送旧机制 1存库不推送,2推送存库
+                # 暂时注销
+                if have_pkey:
+                    res_data = {'code': 0, 'msg': 'Push it once a minute'}
+                    return JsonResponse(status=200, data=res_data)
+
+                # 数据库读取数据
+                if have_ykey:
+                    uid_push_list = eval(redisObj.get_data(key=ykey))
+                else:
+                    # 从数据库查询出来
+                    uid_push_qs = DevicePushService.query_uid_push(uid)
+                    if not uid_push_qs.exists():
+                        logger.info('消息推送-uid_push 数据不存在')
+                        return JsonResponse(status=200, data={'code': 176, 'msg': 'no uid_push data'})
+                    # 修改redis数据,并设置过期时间为10分钟
+                    uid_push_list = DevicePushService.cache_uid_push(uid_push_qs)
+                    redisObj.set_data(key=ykey, val=str(uid_push_list), expire=600)
+                    if not uid_push_list:
+                        res_data = {'code': 404, 'msg': 'error !'}
+                        return JsonResponse(status=200, data=res_data)
+
+                if not uid_push_list:
+                    res_data = {'code': 0, 'msg': 'uid_push_list not exist'}
+                    return JsonResponse(status=200, data=res_data)
+
+                nickname = uid_push_list[0]['uid_set__nickname']
+                detect_interval = uid_push_list[0]['uid_set__detect_interval']
+                detect_group = uid_push_list[0]['uid_set__detect_group']
+                if not nickname:
+                    nickname = uid
+
+                if detect_group is not None:
+                    if have_dkey:
+                        detect_med_type = 1  # 1为存库不推送
+                    else:
+                        detect_med_type = 2  # 为2的话,既推送,又存库
+                        if SERVER_TYPE != 'Ansjer.cn_config.cn_formal_settings':
+                            new_detect_interval = uid_push_list[0]['uid_set__new_detect_interval']
+                            detect_interval = new_detect_interval if new_detect_interval > 0 else detect_interval
+                            detect_interval = 60 if detect_interval < 60 else detect_interval
+                        redisObj.set_data(key=dkey, val=1, expire=detect_interval - 5)
+                        redisObj.set_data(key=pkey, val=1, expire=60)
+                logger.info('APP消息推送V1接口,是否进行APP推送:{},1为不推送,间隔:{}'.format(detect_med_type, detect_interval))
+                # 旧模式并且没有pkey,重新创建一个
+                if not detect_group and not have_pkey:
+                    redisObj.set_data(key=pkey, val=1, expire=60)
+                auth = oss2.Auth(OSS_STS_ACCESS_KEY, OSS_STS_ACCESS_SECRET)
+                bucket = oss2.Bucket(auth, 'oss-cn-shenzhen.aliyuncs.com', 'apg')
+                kwag_args = {
+                    'uid': uid,
+                    'channel': channel,
+                    'event_type': event_type,
+                    'n_time': n_time,
+                }
+                params = {'nickname': nickname, 'uid': uid, 'kwag_args': kwag_args, 'is_st': is_st,
+                          'is_sys_msg': is_sys_msg, 'channel': channel, 'event_type': event_type, 'n_time': n_time,
+                          'electricity': '', 'bucket': bucket, 'app_push': ''}
+                #  推送以及报警消息存库
+                result = DevicePushService.save_msg_push(uid_set_push_list=uid_push_list, **params)
+                if result['code_date'] is None:
+                    result['code_date'] = {'do_apns_code': '', 'do_fcm_code': '', 'do_jpush_code': ''}
+                if detect_med_type == 1:
+                    result['code_date']['do_apns_code'] = '只存库不推送'
+                    result['code_date']['do_fcm_code'] = '只存库不推送'
+                    result['code_date']['do_jpush_code'] = '只存库不推送'
                 if is_sys_msg:
-                    sys_msg_text = self.get_msg_text(channel=channel, n_time=n_time, lang=lang, tz=tz,
-                                                     event_type=event_type, is_sys=1)
-                    sys_msg_list.append(SysMsgModel(
-                        userID_id=userID_id,
-                        msg=sys_msg_text,
-                        addTime=now_time,
-                        updTime=now_time,
-                        uid=uid,
-                        eventType=event_type))
+                    SysMsgModel.objects.bulk_create(result['sys_msg_list'])
                 else:
-                    logger.info('----《旧接口》start------')
-                    new_device_info_list.append(EquipmentInfoService.get_equipment_info_obj(
-                        local_date_time,
-                        device_user_id=userID_id,
-                        event_time=n_time,
-                        event_type=event_type,
-                        device_uid=uid,
-                        device_nick_name=nickname,
-                        channel=channel,
-                        alarm='Motion \tChannel:{channel}'.format(channel=channel),
-                        is_st=is_st,
-                        receive_time=n_time,
-                        add_time=now_time,
-                        storage_location=1,
-                        border_coords='',
-                    ))
-                userID_ids.append(userID_id)
-
-            # 推送
-            if detect_med_type == 2 or detect_med_type == 0:
-                logger.info('准备推送{}'.format(detect_med_type))
-                try:
-                    if push_type == 0:  # ios apns
-                        do_apns_code = self.do_apns(**kwag_args)
-                    elif push_type == 1:  # android gcm
-                        print('do_fcm')
-                        do_fcm_code = self.do_fcm(**kwag_args)
-                    elif push_type == 2:  # android jpush
-                        print('do_jpush')
-                        do_jpush_code = self.do_jpush(**kwag_args)
-                except Exception as e:
-                    logger.info("errLine={errLine}, errMsg={errMsg}".format(errLine=e.__traceback__.tb_lineno,
-                                                                            errMsg=repr(e)))
-                    continue
-
-            if detect_med_type == 1:
-                do_apns_code = '只存库不推送'
-                do_fcm_code = '只存库不推送'
-                do_jpush_code = '只存库不推送'
-        if is_sys_msg:
-            SysMsgModel.objects.bulk_create(sys_msg_list)
-        else:
-            if new_device_info_list and len(new_device_info_list) > 0:
-                # 根据日期获得星期几
-                week = LocalDateTimeUtil.date_to_week(local_date_time)
-                EquipmentInfoService.equipment_info_bulk_create(week, new_device_info_list)
-                logger.info('----《旧接口》设备信息分表批量保存end')
-        if is_st == '0' or is_st == '2':
-            print("is_st=0or2")
-            for up in uid_push_list:
-                if up['push_type'] == 0:  # ios apns
-                    up['do_apns_code'] = do_apns_code
-                elif up['push_type'] == 1:  # android gcm
-                    up['do_fcm_code'] = do_fcm_code
-                elif up['push_type'] == 2:  # android jpush
-                    up['do_jpush_code'] = do_jpush_code
-                del up['push_type']
-                del up['userID_id']
-                del up['userID__NickName']
-                del up['lang']
-                del up['tz']
-                del up['uid_set__nickname']
-                del up['uid_set__detect_interval']
-                del up['uid_set__detect_group']
-            return JsonResponse(status=200, data={'code': 0, 'msg': 'success 0 or 2'})
-
-        elif is_st == '1':
-            print("is_st=1")
-            # Endpoint以杭州为例,其它Region请按实际情况填写。
-            obj = '{uid}/{channel}/{filename}.jpeg'.format(uid=uid, channel=channel, filename=n_time)
-            # 设置此签名URL在60秒内有效。
-            url = bucket.sign_url('PUT', obj, 7200)
-            for up in uid_push_list:
-                up['do_apns_code'] = do_apns_code
-                up['do_fcm_code'] = do_fcm_code
-                up['do_jpush_code'] = do_jpush_code
-                del up['push_type']
-                del up['userID_id']
-                del up['userID__NickName']
-                del up['lang']
-                del up['tz']
-                del up['uid_set__nickname']
-                del up['uid_set__detect_interval']
-                del up['uid_set__detect_group']
-            res_data = {'code': 0, 'img_push': url, 'msg': 'success'}
-            return JsonResponse(status=200, data=res_data)
-
-        elif is_st == '3':
-            print("is_st=3")
-            # 人形检测带动图
-            # Endpoint以杭州为例,其它Region请按实际情况填写。
-            img_url_list = []
-            for i in range(int(is_st)):
-                obj = '{uid}/{channel}/{filename}_{st}.jpeg'. \
-                    format(uid=uid, channel=channel, filename=n_time, st=i)
-                # 设置此签名URL在60秒内有效。
-                url = bucket.sign_url('PUT', obj, 7200)
-                img_url_list.append(url)
-
-            for up in uid_push_list:
-                up['do_apns_code'] = do_apns_code
-                up['do_fcm_code'] = do_fcm_code
-                up['do_jpush_code'] = do_jpush_code
-                del up['push_type']
-                del up['userID_id']
-                del up['userID__NickName']
-                del up['lang']
-                del up['tz']
-                del up['uid_set__nickname']
-                del up['uid_set__detect_interval']
-                del up['uid_set__detect_group']
-
-            res_data = {'code': 0, 'img_url_list': img_url_list, 'msg': 'success 3'}
-            return JsonResponse(status=200, data=res_data)
-
-    def get_msg_title(self, appBundleId, nickname):
-        package_title_config = {
-            'com.ansjer.customizedd_a': 'DVS',
-            'com.ansjer.zccloud_a': 'ZosiSmart',
-            'com.ansjer.zccloud_ab': '周视',
-            'com.ansjer.adcloud_a': 'ADCloud',
-            'com.ansjer.adcloud_ab': 'ADCloud',
-            'com.ansjer.accloud_a': 'ACCloud',
-            'com.ansjer.loocamccloud_a': 'Loocam',
-            'com.ansjer.loocamdcloud_a': 'Anlapus',
-            'com.ansjer.customizedb_a': 'COCOONHD',
-            'com.ansjer.customizeda_a': 'Guardian365',
-            'com.ansjer.customizedc_a': 'PatrolSecure',
-        }
-        if appBundleId in package_title_config.keys():
-            return package_title_config[appBundleId] + '(' + nickname + ')'
-        else:
-            return nickname
+                    if result['new_device_info_list'] and len(result['new_device_info_list']) > 0:
+                        # 根据日期获得星期几
+                        week = LocalDateTimeUtil.date_to_week(result['local_date_time'])
+                        EquipmentInfoService.equipment_info_bulk_create(week, result['new_device_info_list'])
+                        logger.info('----《旧接口》设备信息分表批量保存end')
+                if is_st == '0' or is_st == '2':
+                    print("is_st=0or2")
+                    for up in uid_push_list:
+                        if up['push_type'] == 0:  # ios apns
+                            up['do_apns_code'] = result['code_date']['do_apns_code']
+                        elif up['push_type'] == 1:  # android gcm
+                            up['do_fcm_code'] = result['code_date']['do_fcm_code']
+                        elif up['push_type'] == 2:  # android jpush
+                            up['do_jpush_code'] = result['code_date']['do_jpush_code']
+                        del up['push_type']
+                        del up['userID_id']
+                        del up['userID__NickName']
+                        del up['lang']
+                        del up['tz']
+                        del up['uid_set__nickname']
+                        del up['uid_set__detect_interval']
+                        del up['uid_set__detect_group']
+                    return JsonResponse(status=200, data={'code': 0, 'msg': 'success 0 or 2'})
+
+                elif is_st == '1':
+                    print("is_st=1")
+                    # Endpoint以杭州为例,其它Region请按实际情况填写。
+                    obj = '{uid}/{channel}/{filename}.jpeg'.format(uid=uid, channel=channel, filename=n_time)
+                    # 设置此签名URL在60秒内有效。
+                    url = bucket.sign_url('PUT', obj, 7200)
+                    for up in uid_push_list:
+                        up['do_apns_code'] = result['code_date']['do_apns_code']
+                        up['do_fcm_code'] = result['code_date']['do_fcm_code']
+                        up['do_jpush_code'] = result['code_date']['do_jpush_code']
+                        del up['push_type']
+                        del up['userID_id']
+                        del up['userID__NickName']
+                        del up['lang']
+                        del up['tz']
+                        del up['uid_set__nickname']
+                        del up['uid_set__detect_interval']
+                        del up['uid_set__detect_group']
+                    res_data = {'code': 0, 'img_push': url, 'msg': 'success'}
+                    return JsonResponse(status=200, data=res_data)
+
+                elif is_st == '3':
+                    print("is_st=3")
+                    # 人形检测带动图
+                    # Endpoint以杭州为例,其它Region请按实际情况填写。
+                    img_url_list = []
+                    for i in range(int(is_st)):
+                        obj = '{uid}/{channel}/{filename}_{st}.jpeg'. \
+                            format(uid=uid, channel=channel, filename=n_time, st=i)
+                        # 设置此签名URL在60秒内有效。
+                        url = bucket.sign_url('PUT', obj, 7200)
+                        img_url_list.append(url)
+
+                    for up in uid_push_list:
+                        up['do_apns_code'] = result['code_date']['do_apns_code']
+                        up['do_fcm_code'] = result['code_date']['do_fcm_code']
+                        up['do_jpush_code'] = result['code_date']['do_jpush_code']
+                        del up['push_type']
+                        del up['userID_id']
+                        del up['userID__NickName']
+                        del up['lang']
+                        del up['tz']
+                        del up['uid_set__nickname']
+                        del up['uid_set__detect_interval']
+                        del up['uid_set__detect_group']
+
+                    res_data = {'code': 0, 'img_url_list': img_url_list, 'msg': 'success 3'}
+                    return JsonResponse(status=200, data=res_data)
+        except Exception as e:
+            logger.info('消息推送-异常详情,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+            data = {
+                'errLine': e.__traceback__.tb_lineno,
+                'errMsg': repr(e),
+            }
+            return JsonResponse(status=200, data=json.dumps(data), safe=False)
 
     def is_sys_msg(self, event_type):
         event_type_list = [702, 703, 704]
         if event_type in event_type_list:
             return True
         return False
-
-    def get_msg_text(self, channel, n_time, lang, tz, event_type, is_sys=0):
-        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
-        etype = int(event_type)
-        if lang == 'cn':
-            if etype == 704:
-                msg_type = '电量过低'
-            elif etype == 702:
-                msg_type = '摄像头休眠'
-            elif etype == 703:
-                msg_type = '摄像头唤醒'
-            else:
-                msg_type = ''
-            if is_sys:
-                send_text = '{msg_type} 通道:{channel}'.format(msg_type=msg_type, channel=channel)
-            else:
-                send_text = '{msg_type} 通道:{channel} 日期:{date}'.format(msg_type=msg_type, channel=channel, date=n_date)
-        else:
-            if etype == 704:
-                msg_type = 'Low battery'
-            elif etype == 702:
-                msg_type = 'Camera sleep'
-            elif etype == 703:
-                msg_type = 'Camera wake'
-            else:
-                msg_type = ''
-            if is_sys:
-                send_text = '{msg_type} channel:{channel}'. \
-                    format(msg_type=msg_type, channel=channel)
-            else:
-                send_text = '{msg_type} channel:{channel} date:{date}'. \
-                    format(msg_type=msg_type, channel=channel, date=n_date)
-        return send_text
-
-    def do_jpush(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
-        app_key = JPUSH_CONFIG[appBundleId]['Key']
-        master_secret = JPUSH_CONFIG[appBundleId]['Secret']
-        _jpush = jpush.JPush(app_key, master_secret)
-        push = _jpush.create_push()
-        push.audience = jpush.registration_id(token_val)
-        push_data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
-                     "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
-        android = jpush.android(alert=msg_text, priority=1, style=1, alert_type=7,
-                                big_text=msg_text, title=msg_title,
-                                extras=push_data)
-        push.notification = jpush.notification(android=android)
-        push.platform = jpush.all_
-        try:
-            res = push.send()
-            print(res)
-            status_code = res.status_code
-        except Exception as e:
-            logger = logging.getLogger('info')
-            logger.info(e)
-            status_code = 100
-        return status_code
-
-    def do_fcm(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
-        try:
-            serverKey = FCM_CONFIG[appBundleId]
-        except Exception as e:
-            return 'serverKey abnormal'
-        push_service = FCMNotification(api_key=serverKey)
-        data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
-                "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
-        result = push_service.notify_single_device(registration_id=token_val, message_title=msg_title,
-                                                   message_body=msg_text, data_message=data,
-                                                   extra_kwargs={
-                                                       'default_vibrate_timings': True,
-                                                       'default_sound': True,
-                                                       'default_light_settings': True
-                                                   })
-        return result
-
-    def do_apns(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
-        try:
-            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)
-            payload = apns2.Payload(alert=alert, custom=push_data, sound="default")
-            n = apns2.Notification(payload=payload, priority=apns2.PRIORITY_LOW)
-            res = cli.push(n=n, device_token=token_val, topic=appBundleId)
-            if res.status_code == 200:
-                return res.status_code
-            else:
-                return res.status_code
-        except (ValueError, ArithmeticError):
-            return 'The program has a numeric format exception, one of the arithmetic exceptions'
-        except Exception as e:
-            return repr(e)

+ 90 - 289
Controller/DetectControllerV2.py

@@ -1,29 +1,23 @@
 import json
 import logging
 import os
-import threading
-import time
 
 import apns2
 import boto3
 import botocore
 import jpush as jpush
 from botocore import client
+from django.db import transaction
 from django.http import JsonResponse
 from django.views.generic.base import View
 from pyfcm import FCMNotification
 
 from AnsjerPush.config import AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
 from AnsjerPush.config import JPUSH_CONFIG, FCM_CONFIG, APNS_CONFIG, BASE_DIR, APNS_MODE
-from AnsjerPush.config import SERVER_TYPE
-from Model.models import UidPushModel, SysMsgModel
-from Object.ETkObject import ETkObject
 from Object.RedisObject import RedisObject
-from Object.UidTokenObject import UidTokenObject
-from Object.utils import LocalDateTimeUtil
 from Service.CommonService import CommonService
-from Service.EquipmentInfoService import EquipmentInfoService
-from Service.GatewayService import GatewayPushService
+from Service.DevicePushService import DevicePushService
+from Service.PushService import PushObject
 
 
 # 移动侦测V2接口
@@ -38,6 +32,17 @@ class NotificationV2View(View):
         return self.validation(request.POST)
 
     def validation(self, request_dict):
+        """
+        设备触发报警消息推送
+        @param request_dict:uidToken 加密uid
+        @param request_dict:etk 加密uid
+        @param request_dict:channel 设备通道号
+        @param request_dict:n_time 设备触发报警时间
+        @param request_dict:event_type 设备事件类型
+        @param request_dict:is_st 文件类型(0:无,1:图片,2:视频)
+        @param request_dict:region 文件存储区域(1:国外,2国内)
+        @param request_dict:electricity 电量值
+        """
         logger = logging.getLogger('info')
         logger.info("移动侦测V2接口参数:{}".format(request_dict))
         uidToken = request_dict.get('uidToken', None)
@@ -57,252 +62,71 @@ class NotificationV2View(View):
             return JsonResponse(status=200, data={'code': 404, 'msg': 'no region or is_st'})
 
         try:
-            is_st = int(is_st)
-            region = int(region)
-
-            # 解密获取uid
-            if etk:
-                eto = ETkObject(etk)
-                uid = eto.uid
-            else:
-                uto = UidTokenObject(uidToken)
-                uid = uto.UID
-            # uid = request_dict.get('uid', None)     # 调试
-            # 判断uid长度
-            if len(uid) != 20 and len(uid) != 14:
-                return JsonResponse(status=200, data={'code': 404, 'msg': 'wrong uid'})
-            logger.info('调用推送接口的uid:{}'.format(uid))
-
-            pkey = '{uid}_{channel}_{event_type}_ptl'.format(uid=uid, channel=channel, event_type=event_type)
-            ykey = '{uid}_redis_qs'.format(uid=uid)
-            is_sys_msg = self.is_sys_msg(int(event_type))
-            if is_sys_msg:
-                dkey = '{uid}_{channel}_{event_type}_flag'.format(uid=uid, channel=channel, event_type=event_type)
-            else:
-                dkey = '{uid}_{channel}_flag'.format(uid=uid, channel=channel)
-
-            redisObj = RedisObject(db=6)
-            have_ykey = redisObj.get_data(key=ykey)  # uid_set 数据库缓存
-            have_pkey = redisObj.get_data(key=pkey)  # 一分钟限制key
-            have_dkey = redisObj.get_data(key=dkey)  # 推送消息时间间隔
-            logger.info('ykey:{}, pkey: {}, dkey: {}'.format(have_ykey, have_pkey, have_dkey))
-            # 一分钟内不推送
-            if have_pkey:
-                return JsonResponse(status=200, data={'code': 0, 'msg': 'Push again in one minute'})
-            redisObj.set_data(key=pkey, val=1, expire=60)
-
-            # 查询推送数据
-            uid_push_qs = UidPushModel.objects.filter(uid_set__uid=uid, uid_set__detect_status=1). \
-                values('token_val', 'app_type', 'appBundleId', 'm_code', 'push_type', 'userID_id', 'userID__NickName',
-                       'lang', 'm_code', 'tz', 'uid_set__nickname', 'uid_set__detect_interval', 'uid_set__detect_group',
-                       'uid_set__channel', 'uid_set__ai_type', 'uid_set__new_detect_interval')
-            if not uid_push_qs.exists():
-                logger.info('uid_push 数据不存在')
-                return JsonResponse(status=200, data={'code': 176, 'msg': 'no uid_push data'})
-            ai_type = uid_push_qs.first()['uid_set__ai_type']
-            event_type = self.get_combo_msg_type(ai_type, int(event_type))
-            redis_list = []
-            for qs in uid_push_qs:
-                redis_list.append(qs)
-            # 修改redis数据,并设置过期时间为10分钟
-            redisObj.set_data(key=ykey, val=str(redis_list), expire=600)
-
-            nickname = redis_list[0]['uid_set__nickname']
-            detect_interval = redis_list[0]['uid_set__detect_interval']
-            if not nickname:
-                nickname = uid
-            logger.info('{}推送nickname:{}'.format(uid, nickname))
-            if not have_dkey:
-                # 设置推送消息的时间间隔
-                if SERVER_TYPE != 'Ansjer.cn_formal_settings':
-                    new_detect_interval = redis_list[0]['uid_set__new_detect_interval']
-                    detect_interval = new_detect_interval if new_detect_interval > 0 else detect_interval
-                    detect_interval = 60 if detect_interval < 60 else detect_interval
-                redisObj.set_data(key=dkey, val=1, expire=detect_interval - 5)
-                logger.info('APP消息推送间隔:{}s'.format(detect_interval))
-
-            if is_st == 1 or is_st == 3:  # 使用aws s3
-                aws_s3_client = s3_client(region=region)
-                bucket = 'foreignpush' if region == 1 else 'push'
-            kwag_args = {
-                'uid': uid,
-                'channel': channel,
-                'event_type': event_type,
-                'n_time': n_time,
-            }
-            sys_msg_list = []
-            userID_ids = []
-            do_apns_code = ''
-            do_fcm_code = ''
-            do_jpush_code = ''
-            logger.info('进入手机推送------')
-            logger.info('uid={}'.format(uid))
-            logger.info(redis_list)
-            new_device_info_list = []
-            local_date_time = ''
-            for up in redis_list:
-                push_type = up['push_type']
-                appBundleId = up['appBundleId']
-                token_val = up['token_val']
-                lang = up['lang']
-                tz = up['tz']
-                if tz is None or tz == '':
-                    tz = 0
-                # 发送标题
-                msg_title = self.get_msg_title(appBundleId=appBundleId, nickname=nickname)
-                logger.info('{}推送标题{}'.format(uid, msg_title))
-                # 发送内容
-                msg_text = self.get_msg_text(channel=channel, n_time=n_time, lang=lang, tz=tz,
-                                             event_type=event_type, electricity=electricity)
-                kwag_args['appBundleId'] = appBundleId
-                kwag_args['token_val'] = token_val
-                kwag_args['msg_title'] = msg_title
-                kwag_args['msg_text'] = msg_text
-                logger.info('推送要的数据: {}'.format(kwag_args))
-                local_date_time = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang='cn')
-                logger.info('<<<<<根据时区计算后日期={},时区={}'.format(local_date_time, tz))
-                local_date_time = local_date_time[0:10]
-                logger.info('<<<<<切片后的日期={}'.format(local_date_time))
-                # 以下是存库
-                userID_id = up["userID_id"]
-                if userID_id not in userID_ids:
-                    now_time = int(time.time())
-                    if is_sys_msg:
-                        sys_msg_text = self.get_msg_text(channel=channel, n_time=n_time, lang=lang, tz=tz,
-                                                         event_type=event_type, electricity=electricity, is_sys=1)
-                        sys_msg_list.append(SysMsgModel(
-                            userID_id=userID_id,
-                            msg=sys_msg_text,
-                            addTime=now_time,
-                            updTime=now_time,
-                            uid=uid,
-                            eventType=event_type))
-                    else:
-                        # start 根据设备侦测时间为准进行分表存储数据
-                        logger.info('分表存数据start------')
-                        new_device_info_list.append(EquipmentInfoService.get_equipment_info_obj(
-                            local_date_time,
-                            device_user_id=userID_id,
-                            event_time=n_time,
-                            event_type=event_type,
-                            device_uid=uid,
-                            device_nick_name=nickname,
-                            channel=channel,
-                            alarm='Motion \tChannel:{channel}'.format(channel=channel),
-                            is_st=is_st,
-                            receive_time=n_time,
-                            add_time=now_time,
-                            storage_location=2,
-                            border_coords='',
-                        ))
-                        # end
-                    userID_ids.append(userID_id)
-                try:
-                    # 推送消息
-                    if not have_dkey:
-                        logger.info('APP准备推送:{}, {}'.format(uid, request_dict))
-                        if (is_st == 1 or is_st == 3) and (push_type == 0 or push_type == 1):  # 推送显示图片
-                            if is_st == 1:
-                                key = '{}/{}/{}.jpeg'.format(uid, channel, n_time)
-                            else:
-                                key = '{}/{}/{}_0.jpeg'.format(uid, channel, n_time)
-                            push_thread = threading.Thread(target=self.push_thread_test, args=(
-                                push_type, aws_s3_client, bucket, key, uid, appBundleId, token_val, event_type, n_time,
-                                msg_title, msg_text, channel))
-                            push_thread.start()
-                        else:
-                            if push_type == 0:  # ios apns
-                                do_apns_code = self.do_apns(**kwag_args)
-                            elif push_type == 1:  # android gcm
-                                do_fcm_code = self.do_fcm(**kwag_args)
-                            elif push_type == 2:  # android jpush
-                                do_jpush_code = self.do_jpush(**kwag_args)
-                except Exception as e:
-                    logger.info(
-                        "errLine={errLine}, errMsg={errMsg}".format(errLine=e.__traceback__.tb_lineno, errMsg=repr(e)))
-                    continue
-            if is_sys_msg:
-                SysMsgModel.objects.bulk_create(sys_msg_list)
-            else:
-                # new 分表批量存储 设备信息
-                if new_device_info_list and len(new_device_info_list) > 0:
-                    # 根据日期获得星期几
-                    week = LocalDateTimeUtil.date_to_week(local_date_time)
-                    EquipmentInfoService.equipment_info_bulk_create(week, new_device_info_list)
-                    logger.info('设备信息分表批量保存end------')
-
-            if is_st == 0 or is_st == 2:
-                for up in redis_list:
-                    if up['push_type'] == 0:  # ios apns
-                        up['do_apns_code'] = do_apns_code
-                    elif up['push_type'] == 1:  # android gcm
-                        up['do_fcm_code'] = do_fcm_code
-                    elif up['push_type'] == 2:  # android jpush
-                        up['do_jpush_code'] = do_jpush_code
-                    del up['push_type']
-                    del up['userID_id']
-                    del up['userID__NickName']
-                    del up['lang']
-                    del up['tz']
-                    del up['uid_set__nickname']
-                    del up['uid_set__detect_interval']
-                    del up['uid_set__detect_group']
-                return JsonResponse(status=200, data={'code': 0, 'msg': 'success 0 or 2', 're_list': redis_list})
-
-            elif is_st == 1:
-                thumbspng = '{uid}/{channel}/{filename}.jpeg'.format(uid=uid, channel=channel, filename=n_time)
-                Params = {'Key': thumbspng}
-                if region == 2:  # 2:国内
-                    Params['Bucket'] = 'push'
-                else:  # 1:国外
-                    Params['Bucket'] = 'foreignpush'
-                response_url = generate_s3_url(aws_s3_client, Params)
-                for up in redis_list:
-                    up['do_apns_code'] = do_apns_code
-                    up['do_fcm_code'] = do_fcm_code
-                    up['do_jpush_code'] = do_jpush_code
-                    del up['push_type']
-                    del up['userID_id']
-                    del up['userID__NickName']
-                    del up['lang']
-                    del up['tz']
-                    del up['uid_set__nickname']
-                    del up['uid_set__detect_interval']
-                    del up['uid_set__detect_group']
-                res_data = {'code': 0, 'img_push': response_url, 'msg': 'success'}
-                return JsonResponse(status=200, data=res_data)
-
-            elif is_st == 3:
-                img_url_list = []
-                if region == 2:  # 2:国内
-                    Params = {'Bucket': 'push'}
-                else:  # 1:国外
-                    Params = {'Bucket': 'foreignpush'}
-                for i in range(is_st):
-                    thumbspng = '{uid}/{channel}/{filename}_{st}.jpeg'. \
-                        format(uid=uid, channel=channel, filename=n_time, st=i)
-                    Params['Key'] = thumbspng
-                    response_url = generate_s3_url(aws_s3_client, Params)
-                    img_url_list.append(response_url)
-
-                for up in redis_list:
-                    up['do_apns_code'] = do_apns_code
-                    up['do_fcm_code'] = do_fcm_code
-                    up['do_jpush_code'] = do_jpush_code
-                    del up['push_type']
-                    del up['userID_id']
-                    del up['userID__NickName']
-                    del up['lang']
-                    del up['tz']
-                    del up['uid_set__nickname']
-                    del up['uid_set__detect_interval']
-                    del up['uid_set__detect_group']
-                res_data = {'code': 0, 'img_url_list': img_url_list, 'msg': 'success 3'}
-                return JsonResponse(status=200, data=res_data)
+            with transaction.atomic():
+                is_st = int(is_st)
+                region = int(region)
+                uid = DevicePushService.decode_uid(etk, uidToken)  # 解密uid
+                if len(uid) != 20 and len(uid) != 14:
+                    return JsonResponse(status=200, data={'code': 404, 'msg': 'wrong uid'})
+                req_limiting = '{uid}_{channel}_{event_type}_ptl' \
+                    .format(uid=uid, channel=channel, event_type=event_type)
+                is_sys_msg = self.is_sys_msg(int(event_type))  # 判断事件类型是否是系统消息
+                if is_sys_msg:
+                    push_interval = '{uid}_{channel}_{event_type}_flag' \
+                        .format(uid=uid, channel=channel, event_type=event_type)
+                else:
+                    push_interval = '{uid}_{channel}_flag'.format(uid=uid, channel=channel)
+                redisObj = RedisObject(db=6)
+                cache_req_limiting = redisObj.get_data(key=req_limiting)  # 获取请求限流缓存数据
+                cache_app_push = redisObj.get_data(key=push_interval)  # 获取APP推送消息时间间隔缓存数据
+                logger.info('消息推送- 限流key: {}, 推送间隔key: {}'.
+                            format(cache_req_limiting, cache_app_push))
+                if cache_req_limiting:  # 限流存在则直接返回
+                    return JsonResponse(status=200, data={'code': 0, 'msg': 'Push again in one minute'})
+                redisObj.set_data(key=req_limiting, val=1, expire=60)  # 当缓存不存在限流数据 重新设置一分钟请求一次
+                uid_push_qs = DevicePushService.query_uid_push(uid)  # 查询uid_set与push数据列表
+                if not uid_push_qs.exists():
+                    logger.info('消息推送-uid_push 数据不存在')
+                    return JsonResponse(status=200, data={'code': 176, 'msg': 'no uid_push data'})
+                ai_type = uid_push_qs.first()['uid_set__ai_type']
+                event_type = self.get_combo_msg_type(ai_type, int(event_type))  # 解析消息事件类型看是否多类型组合
+                # 将uid_set以及uid_push 转数组列表
+                uid_set_push_list = DevicePushService.cache_uid_push(uid_push_qs)
+                nickname = uid_set_push_list[0]['uid_set__nickname']
+                nickname = uid if not nickname else nickname
+                # APP消息提醒推送间隔
+                detect_interval = uid_set_push_list[0]['uid_set__detect_interval']
+                if not cache_app_push:
+                    # 缓存APP提醒推送间隔 默认1分钟提醒一次
+                    DevicePushService.cache_push_detect_interval(redisObj, push_interval, detect_interval,
+                                                                 uid_set_push_list[0]['uid_set__new_detect_interval'])
+                bucket = ''
+                aws_s3_client = ''
+                if is_st == 1 or is_st == 3:  # 使用aws s3
+                    aws_s3_client = s3_client(region=region)
+                    bucket = 'foreignpush' if region == 1 else 'push'
+                kwag_args = {
+                    'uid': uid,
+                    'channel': channel,
+                    'event_type': event_type,
+                    'n_time': n_time,
+                }
+                params = {'nickname': nickname, 'uid': uid, 'kwag_args': kwag_args, 'is_st': is_st, 'region': region,
+                          'is_sys_msg': is_sys_msg, 'channel': channel, 'event_type': event_type, 'n_time': n_time,
+                          'electricity': electricity, 'bucket': bucket, 'aws_s3_client': aws_s3_client,
+                          'app_push': cache_app_push}
+                # APP消息推送与获取报警消息数据列表
+                result = DevicePushService.save_msg_push(uid_set_push_list, **params)
+                # 批量系统消息&报警消息数据存库
+                DevicePushService.save_sys_msg(is_sys_msg, result['local_date_time'],
+                                               result['sys_msg_list'], result['new_device_info_list'])
+                params['aws_s3_client'] = aws_s3_client
+                params['uid_set_push_list'] = uid_set_push_list
+                params['code_dict'] = result
+                result_dict = DevicePushService.get_push_url(**params)  # 获取S3对象上传链接
+                return JsonResponse(status=200, data=result_dict)
         except Exception as e:
-            logger.info('移动侦测接口异常: {}'.format(e))
-            logger.info('错误文件', e.__traceback__.tb_frame.f_globals['__file__'])
-            logger.info('错误行号', e.__traceback__.tb_lineno)
+            logger.info('消息推送-异常详情,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
             data = {
                 'errLine': e.__traceback__.tb_lineno,
                 'errMsg': repr(e),
@@ -362,34 +186,20 @@ class NotificationV2View(View):
                                                              ExpiresIn=300)
             logger.info('推送图片url:{}'.format(image_url))
             if push_type == 0:
-                GatewayPushService.ios_apns_push(uid, appBundleId, token_val, n_time, event_type, msg_title, msg_text,
-                                                 uid, channel, image_url)
+                PushObject.ios_apns_push(uid, appBundleId, token_val, n_time, event_type, msg_title, msg_text,
+                                         uid, channel, image_url)
             elif push_type == 1:
-                GatewayPushService.android_fcm_push(uid, appBundleId, token_val, n_time, event_type, msg_title,
-                                                    msg_text, uid, channel, image_url)
+                PushObject.android_fcm_push(uid, appBundleId, token_val, n_time, event_type, msg_title,
+                                            msg_text, uid, channel, image_url)
         except Exception as e:
             logger.info('推送图片测试异常:{}'.format(e))
 
-    def get_msg_title(self, appBundleId, nickname):
-        package_title_config = {
-            'com.ansjer.customizedd_a': 'DVS',
-            'com.ansjer.zccloud_a': 'ZosiSmart',
-            'com.ansjer.zccloud_ab': '周视',
-            'com.ansjer.adcloud_a': 'ADCloud',
-            'com.ansjer.adcloud_ab': 'ADCloud',
-            'com.ansjer.accloud_a': 'ACCloud',
-            'com.ansjer.loocamccloud_a': 'Loocam',
-            'com.ansjer.loocamdcloud_a': 'Anlapus',
-            'com.ansjer.customizedb_a': 'COCOONHD',
-            'com.ansjer.customizeda_a': 'Guardian365',
-            'com.ansjer.customizedc_a': 'PatrolSecure',
-        }
-        if appBundleId in package_title_config.keys():
-            return package_title_config[appBundleId] + '(' + nickname + ')'
-        else:
-            return nickname
-
-    def is_sys_msg(self, event_type):
+    @staticmethod
+    def is_sys_msg(event_type):
+        """
+        判断是否属于系统消息
+        @return: True | False
+        """
         event_type_list = [702, 703, 704]
         if event_type in event_type_list:
             return True
@@ -400,7 +210,7 @@ class NotificationV2View(View):
         etype = int(event_type)
         if lang == 'cn':
             if etype == 704:
-                msg_type = '剩余电量:' + electricity
+                msg_type = '剩余电量 ' + electricity
             elif etype == 702:
                 msg_type = '摄像头休眠'
             elif etype == 703:
@@ -413,7 +223,7 @@ class NotificationV2View(View):
                 send_text = '{msg_type} 通道:{channel} 日期:{date}'.format(msg_type=msg_type, channel=channel, date=n_date)
         else:
             if etype == 704:
-                msg_type = 'Battery remaining:' + electricity
+                msg_type = 'Battery remaining ' + electricity
             elif etype == 702:
                 msg_type = 'Camera sleep'
             elif etype == 703:
@@ -523,12 +333,3 @@ def s3_client(region):
             region_name='us-east-1'
         )
     return aws_s3_client
-
-
-def generate_s3_url(aws_s3_client, Params):
-    response_url = aws_s3_client.generate_presigned_url(
-        ClientMethod='put_object',
-        Params=Params,
-        ExpiresIn=3600
-    )
-    return response_url

+ 69 - 177
Controller/PowerWarningController.py

@@ -5,24 +5,19 @@
 @File :PowerWarningController.py
 """
 import logging
-import os
 import time
 
-import apns2
-import jpush as jpush
 from django.http import JsonResponse
 from django.views.generic.base import View
-from pyfcm import FCMNotification
 
-from AnsjerPush.config import JPUSH_CONFIG, FCM_CONFIG, APNS_CONFIG, BASE_DIR, APNS_MODE
-from Model.models import UidPushModel, SysMsgModel
+from Model.models import UidPushModel, SysMsgModel, Device_Info
 from Object.RedisObject import RedisObject
-from Service.CommonService import CommonService
+from Service.HuaweiPushService.HuaweiPushService import HuaweiPushObject
+from Service.PushService import PushObject
 
 
-# 低电量推送接口
 class PowerWarningView(View):
-
+    # 低电量推送接口
     def get(self, request, *args, **kwargs):
         request.encoding = 'utf-8'
         return self.validation(request.GET)
@@ -31,14 +26,15 @@ class PowerWarningView(View):
         request.encoding = 'utf-8'
         return self.validation(request.POST)
 
-    def validation(self, request_dict):
+    @staticmethod
+    def validation(request_dict):
         logger = logging.getLogger('info')
         uid = request_dict.get('uid', None)
 
         # 限制每6小时推一次
-        redisObj = RedisObject()
-        is_limit = redisObj.CONN.setnx(uid+'limit_power_warning', 1)
-        redisObj.CONN.expire(uid+'limit_power_warning', 6*60*60)
+        redis_obj = RedisObject()
+        is_limit = redis_obj.CONN.setnx(uid + 'limit_power_warning', 1)
+        redis_obj.CONN.expire(uid + 'limit_power_warning', 6 * 60 * 60)
         if not is_limit:
             return JsonResponse(status=200, data={'code': 0, 'msg': 'push limited!'})
 
@@ -47,190 +43,86 @@ class PowerWarningView(View):
         logger.info('调用低电量推送接口的uid: {},electricity: {}'.format(uid, electricity))
         try:
             uid_push_qs = UidPushModel.objects.filter(uid_set__uid=uid). \
-                values('token_val', 'app_type', 'appBundleId', 'm_code',
-                       'push_type', 'userID_id', 'userID__NickName',
-                       'lang', 'm_code', 'tz', 'uid_set__nickname', 'uid_set__detect_interval', 'uid_set__detect_group',
-                       'uid_set__channel')
+                values('push_type', 'appBundleId', 'token_val', 'lang', 'tz', 'userID_id')
             if not uid_push_qs.exists():
                 res_data = {'code': 173, 'msg': 'uid push data not exit!'}
                 return JsonResponse(status=200, data=res_data)
 
-            # 新建一个list接收数据
-            redis_list = []
-            # 把数据库数据追加进redis_list
-            for qs in uid_push_qs:
-                redis_list.append(qs)
-
-            if not redis_list:
-                res_data = {'code': 0, 'msg': 'no redis_list success!'}
-                return JsonResponse(status=200, data=res_data)
-
-            nickname = redis_list[0]['uid_set__nickname']
-            if not nickname:
-                nickname = uid
+            uid_push_list = [uid_push for uid_push in uid_push_qs]
+            # 查询设备数据
+            device_info_qs = Device_Info.objects.filter(UID=uid).first()
+            nickname = uid if device_info_qs is None else device_info_qs.NickName
 
             now_time = int(time.time())
-            channel = channel
             event_type = 704
             sys_msg_list = []
-            userID_ids = []
-            kwag_args = {
-                'uid': uid,
-                'channel': channel,
-                'event_type': event_type,
-                'n_time': now_time,
-            }
-
-            for up in redis_list:
-                push_type = up['push_type']
-                appBundleId = up['appBundleId']
-                token_val = up['token_val']
-                lang = up['lang']
-                tz = up['tz']
+            user_id_list = []
+
+            for uid_push in uid_push_list:
+                push_type = uid_push['push_type']
+                app_bundle_id = uid_push['appBundleId']
+                token_val = uid_push['token_val']
+                lang = uid_push['lang']
+                tz = uid_push['tz']
                 if tz is None or tz == '':
                     tz = 0
-                # 发送标题
-                msg_title = self.get_msg_title(appBundleId=appBundleId, nickname=nickname)
-                # 发送内容
-                msg_text = self.get_msg_text(channel=channel, n_time=now_time, lang=lang, tz=tz,
-                                             event_type=event_type, electricity=electricity)
-                kwag_args['appBundleId'] = appBundleId
-                kwag_args['token_val'] = token_val
-                kwag_args['msg_title'] = msg_title
-                kwag_args['msg_text'] = msg_text
-
-                if push_type == 0:  # ios apns
-                    self.do_apns(**kwag_args)
-                elif push_type == 1:  # android gcm
-                    self.do_fcm(**kwag_args)
-                elif push_type == 2:  # android jpush
-                    self.do_jpush(**kwag_args)
 
-                # 以下是存库
-                userID_id = up["userID_id"]
-                if userID_id not in userID_ids:
-                    sys_msg_text = self.get_msg_text(channel=channel, n_time=now_time, lang=lang, tz=tz,
-                                                     event_type=event_type, is_sys=1, electricity=electricity)
+                # 推送标题和推送内容
+                msg_title = PushObject.get_msg_title(nickname=nickname)
+                msg_text = PushObject.get_low_power_msg_text(channel=channel, n_time=now_time, lang=lang, tz=tz,
+                                                             electricity=electricity)
+
+                kwargs = {
+                    'nickname': nickname,
+                    'app_bundle_id': app_bundle_id,
+                    'token_val': token_val,
+                    'n_time': now_time,
+                    'event_type': event_type,
+                    'msg_title': msg_title,
+                    'msg_text': msg_text,
+                    'uid': uid,
+                    'channel': channel,
+                }
+
+                try:
+                    # 推送消息
+                    if push_type == 0:  # ios apns
+                        PushObject.ios_apns_push(**kwargs)
+                    elif push_type == 1:  # android gcm
+                        PushObject.android_fcm_push(**kwargs)
+                    elif push_type == 2:  # android jpush
+                        PushObject.android_jpush(**kwargs)
+                    elif push_type == 3:
+                        huawei_push_object = HuaweiPushObject()
+                        huawei_push_object.send_push_notify_message(**kwargs)
+                    elif push_type == 4:  # android 小米推送
+                        PushObject.android_xmpush(**kwargs)
+                    elif push_type == 5:  # android vivo推送
+                        PushObject.android_vivopush(**kwargs)
+                    elif push_type == 6:  # android oppo推送
+                        PushObject.android_oppopush(**kwargs)
+                    elif push_type == 7:  # android 魅族推送
+                        PushObject.android_meizupush(**kwargs)
+                except Exception as e:
+                    logger.info('低电量推送消息异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+                    continue
+
+                # 保存系统消息
+                user_id = uid_push['userID_id']
+                if user_id not in user_id_list:
+                    sys_msg_text = PushObject.get_low_power_msg_text(channel=channel, n_time=now_time, lang=lang, tz=tz,
+                                                                     electricity=electricity, is_sys=1)
                     sys_msg_list.append(SysMsgModel(
-                        userID_id=userID_id,
+                        userID_id=user_id,
                         msg=sys_msg_text,
                         addTime=now_time,
                         updTime=now_time,
                         uid=uid,
                         eventType=event_type,
                     ))
-                    userID_ids.append(userID_id)
+                    user_id_list.append(user_id)
             SysMsgModel.objects.bulk_create(sys_msg_list)
             return JsonResponse(status=200, data={'code': 0})
         except Exception as e:
             logger.info('低电量推送接口异常: {}'.format(e))
             return JsonResponse(status=500, data={'msg': 'power warning error'})
-
-    def get_msg_title(self, appBundleId, nickname):
-        package_title_config = {
-            'com.ansjer.customizedd_a': 'DVS',
-            'com.ansjer.zccloud_a': 'ZosiSmart',
-            'com.ansjer.zccloud_ab': '周视',
-            'com.ansjer.adcloud_a': 'ADCloud',
-            'com.ansjer.adcloud_ab': 'ADCloud',
-            'com.ansjer.accloud_a': 'ACCloud',
-            'com.ansjer.loocamccloud_a': 'Loocam',
-            'com.ansjer.loocamdcloud_a': 'Anlapus',
-            'com.ansjer.customizedb_a': 'COCOONHD',
-            'com.ansjer.customizeda_a': 'Guardian365',
-            'com.ansjer.customizedc_a': 'PatrolSecure',
-        }
-        if appBundleId in package_title_config.keys():
-            return package_title_config[appBundleId] + '(' + nickname + ')'
-        else:
-            return nickname
-
-    def is_sys_msg(self, event_type):
-        event_type_list = [702, 703, 704]
-        if event_type in event_type_list:
-            return True
-        return False
-
-    def get_msg_text(self, channel, n_time, lang, tz, event_type, electricity, is_sys=0):
-        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
-        etype = int(event_type)
-        if lang == 'cn':
-            if etype == 704:
-                msg_type = '剩余电量:' + electricity
-            elif etype == 702:
-                msg_type = '摄像头休眠'
-            elif etype == 703:
-                msg_type = '摄像头唤醒'
-            else:
-                msg_type = ''
-            if is_sys:
-                send_text = '{msg_type} 通道:{channel}'.format(msg_type=msg_type, channel=channel)
-            else:
-                send_text = '{msg_type} 通道:{channel} 日期:{date}'.format(msg_type=msg_type, channel=channel, date=n_date)
-        else:
-            if etype == 704:
-                msg_type = 'Battery remaining:' + electricity
-            elif etype == 702:
-                msg_type = 'Camera sleep'
-            elif etype == 703:
-                msg_type = 'Camera wake'
-            else:
-                msg_type = ''
-            if is_sys:
-                send_text = '{msg_type} channel:{channel}'. \
-                    format(msg_type=msg_type, channel=channel)
-            else:
-                send_text = '{msg_type} channel:{channel} date:{date}'. \
-                    format(msg_type=msg_type, channel=channel, date=n_date)
-        return send_text
-
-    def do_jpush(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
-        app_key = JPUSH_CONFIG[appBundleId]['Key']
-        master_secret = JPUSH_CONFIG[appBundleId]['Secret']
-        _jpush = jpush.JPush(app_key, master_secret)
-        push = _jpush.create_push()
-        push.audience = jpush.registration_id(token_val)
-        push_data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
-                     "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
-        android = jpush.android(alert=msg_text, priority=1, style=1, alert_type=7,
-                                big_text=msg_text, title=msg_title,
-                                extras=push_data)
-        push.notification = jpush.notification(android=android)
-        push.platform = jpush.all_
-        res = push.send()
-        return res.status_code
-
-    def do_fcm(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
-        try:
-            serverKey = FCM_CONFIG[appBundleId]
-        except Exception as e:
-            return 'serverKey abnormal'
-        push_service = FCMNotification(api_key=serverKey)
-        data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
-                "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
-        result = push_service.notify_single_device(registration_id=token_val, message_title=msg_title,
-                                                   message_body=msg_text, data_message=data,
-                                                   extra_kwargs={
-                                                       'default_vibrate_timings': True,
-                                                       'default_sound': True,
-                                                       'default_light_settings': True
-                                                   })
-        return result
-
-    def do_apns(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
-        try:
-            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)
-            payload = apns2.Payload(alert=alert, custom=push_data, sound="default")
-            n = apns2.Notification(payload=payload, priority=apns2.PRIORITY_LOW)
-            res = cli.push(n=n, device_token=token_val, topic=appBundleId)
-            if res.status_code == 200:
-                return res.status_code
-            else:
-                return res.status_code
-        except (ValueError, ArithmeticError):
-            return 'The program has a numeric format exception, one of the arithmetic exceptions'
-        except Exception as e:
-            return repr(e)

+ 21 - 3
Controller/ShadowController.py

@@ -14,6 +14,15 @@ from Service.CommonService import CommonService
 
 
 def generate_utk(request):
+    """
+    uid生成etk
+    app测试使用
+    @param request: 请求
+    @request username: username
+    @request password: password
+    @request uid: uid
+    @return : etk
+    """
     request.encoding = 'utf-8'
     response = ResponseObject()
     if request.method == 'GET':
@@ -28,7 +37,7 @@ def generate_utk(request):
     if username and password:
         if username == 'debug_user' and password == 'debug_password':
             etkObj = ETkObject(etk='')
-            etk = etkObj.encrypt(uid)
+            etk = etkObj.encrypt_uid(uid)
             return response.json(0, {'etk': etk})
         else:
             return response.json(404)
@@ -36,8 +45,12 @@ def generate_utk(request):
         return response.json(444, 'username password')
 
 
-# 更新设备影子
 def update_device_shadow(request):
+    """
+    设备生成或更新(复位时)设备影子
+    @param request: 请求
+    @return : JsonResponse
+    """
     request.encoding = 'utf-8'
     if request.method == 'POST':
         request_dict = request.POST
@@ -49,7 +62,7 @@ def update_device_shadow(request):
     logger = logging.getLogger('info')
     logger.info('---更新设备影子---, 使用配置:{}, 参数:{}'.format(SERVER_TYPE, request_dict.dict()))
     # 如果为美国配置,异步请求更新国内和欧洲数据
-    if SERVER_TYPE == 'Ansjer.formal_settings':
+    if SERVER_TYPE == 'Ansjer.us_config.formal_settings':
         domain_name_list = ['push.zositechc.cn', 'push.zositeche.com']
         request_thread = threading.Thread(target=do_request_thread, args=(domain_name_list, request_dict.dict()))
         request_thread.start()
@@ -186,6 +199,11 @@ def update_device_shadow(request):
 
 
 def do_request_thread(domain_name_list, data):
+    """
+    请求线程
+    @param domain_name_list: 域名列表
+    @param data: 请求数据
+    """
     for domain_name in domain_name_list:
         url = 'http://{}/deviceShadow/update'.format(domain_name)
         requests.post(url=url, data=data, timeout=2)

+ 76 - 21
Controller/gatewayController.py

@@ -15,7 +15,10 @@ from Object.ResponseObject import ResponseObject
 from Object.utils import LocalDateTimeUtil
 from Service.CommonService import CommonService
 from Service.EquipmentInfoService import EquipmentInfoService
-from Service.GatewayService import GatewayPushService
+from Service.HuaweiPushService.HuaweiPushService import HuaweiPushObject
+from Service.PushService import PushObject
+
+LOGGER = logging.getLogger('info')
 
 
 class GatewayView(View):
@@ -35,6 +38,8 @@ class GatewayView(View):
             return self.gateway_push(request_dict, response)
         elif operation == 'sceneLogPush':  # 场景日志推送
             return self.scene_log_push(request_dict, response)
+        elif operation == 'socketPush':  # 插座推送
+            return self.socket_msg_push(request_dict, response)
         else:
             return response.json(414)
 
@@ -51,7 +56,6 @@ class GatewayView(View):
         @request_dict alarm: 消息内容
         @request_dict defense: 防御状态,0:撤防,1:防御
         @request_dict sensor_status: 拆动状态,拆动时传参
-        @request_dict sensor_low_power: 低电量
         @param response: 响应对象
         @return: response
         """
@@ -158,8 +162,8 @@ class GatewayView(View):
                     tz = gateway_push['tz'] if gateway_push['tz'] else 0
 
                     # 获取推送所需数据
-                    msg_title = GatewayPushService.get_msg_title(app_bundle_id, nickname)
-                    msg_text = GatewayPushService.get_msg_text(n_time, tz, lang, alarm)
+                    msg_title = PushObject.get_msg_title(nickname)
+                    msg_text = PushObject.get_gateway_msg_text(n_time, tz, lang, alarm)
 
                     kwargs['msg_title'] = msg_title
                     kwargs['msg_text'] = msg_text
@@ -169,11 +173,22 @@ class GatewayView(View):
                     try:
                         # 推送消息
                         if push_type == 0:  # ios apns
-                            GatewayPushService.ios_apns_push(**kwargs)
+                            PushObject.ios_apns_push(**kwargs)
                         elif push_type == 1:  # android gcm
-                            GatewayPushService.android_fcm_push(**kwargs)
+                            PushObject.android_fcm_push(**kwargs)
                         elif push_type == 2:  # android 极光推送
-                            GatewayPushService.android_jpush(**kwargs)
+                            PushObject.android_jpush(**kwargs)
+                        elif push_type == 3:
+                            huawei_push_object = HuaweiPushObject()
+                            huawei_push_object.send_push_notify_message(**kwargs)
+                        elif push_type == 4:  # android 小米推送
+                            PushObject.android_xmpush(**kwargs)
+                        elif push_type == 5:  # android vivo推送
+                            PushObject.android_vivopush(**kwargs)
+                        elif push_type == 6:  # android oppo推送
+                            PushObject.android_oppopush(**kwargs)
+                        elif push_type == 7:  # android 魅族推送
+                            PushObject.android_meizupush(**kwargs)
                     except Exception as e:
                         logger.info('网关推送消息异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
                         continue
@@ -186,14 +201,10 @@ class GatewayView(View):
     @staticmethod
     def scene_log_push(request_dict, response):
         """
-        网关推送
+        网关智能场景日志推送
         @param request_dict: 请求参数
-        @request_dict serial_number: 序列号
-        @request_dict ieee_addr: 长地址
-        @request_dict src_addr: 短地址
-        @request_dict sensor_type: 传感器类型
-        @request_dict event_type: 事件类型
-        @request_dict alarm: 消息内容
+        @request_dict sceneId: 场景id
+        @request_dict status: 状态
         @param response: 响应对象
         @return: response
         """
@@ -209,13 +220,18 @@ class GatewayView(View):
         if not smart_scene_qs.exists():
             return response.json(173)
 
-        nickname = ''
         scene_name = smart_scene_qs[0]['scene_name']
         tasks = smart_scene_qs[0]['tasks']
         device_id = smart_scene_qs[0]['device_id']
         sub_device_id = smart_scene_qs[0]['sub_device_id']
         n_time = int(time.time())
         user_id = smart_scene_qs[0]['user_id']
+        if sub_device_id:
+            gateway_sub_device_qs = GatewaySubDevice.objects.filter(id=sub_device_id).values('nickname')
+            nickname = gateway_sub_device_qs[0]['nickname'] if gateway_sub_device_qs.exists() else ''
+        else:
+            device_qs = Device_Info.objects.filter(id=device_id).values('NickName')
+            nickname = device_qs[0]['NickName'] if device_qs.exists() else ''
         log_dict = {
             'scene_id': scene_id,
             'scene_name': scene_name,
@@ -249,10 +265,8 @@ class GatewayView(View):
                         app_bundle_id = gateway_push['app_bundle_id']
                         push_type = gateway_push['push_type']
                         token_val = gateway_push['token_val']
-                        lang = gateway_push['lang']
-                        tz = gateway_push['tz'] if gateway_push['tz'] else 0
 
-                        kwargs['msg_title'] = nickname
+                        kwargs['msg_title'] = PushObject.get_msg_title(nickname)
                         kwargs['msg_text'] = event_info
                         kwargs['app_bundle_id'] = app_bundle_id
                         kwargs['token_val'] = token_val
@@ -260,11 +274,22 @@ class GatewayView(View):
                         try:
                             # 推送消息
                             if push_type == 0:  # ios apns
-                                GatewayPushService.ios_apns_push(**kwargs)
+                                PushObject.ios_apns_push(**kwargs)
                             elif push_type == 1:  # android gcm
-                                GatewayPushService.android_fcm_push(**kwargs)
+                                PushObject.android_fcm_push(**kwargs)
                             elif push_type == 2:  # android 极光推送
-                                GatewayPushService.android_jpush(**kwargs)
+                                PushObject.android_jpush(**kwargs)
+                            elif push_type == 3:
+                                huawei_push_object = HuaweiPushObject()
+                                huawei_push_object.send_push_notify_message(**kwargs)
+                            elif push_type == 4:  # android 小米推送
+                                PushObject.android_xmpush(**kwargs)
+                            elif push_type == 5:  # android vivo推送
+                                PushObject.android_vivopush(**kwargs)
+                            elif push_type == 6:  # android oppo推送
+                                PushObject.android_oppopush(**kwargs)
+                            elif push_type == 7:  # android 魅族推送
+                                PushObject.android_meizupush(**kwargs)
                         except Exception as e:
                             logger.info('场景日志推送消息异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
                             continue
@@ -272,3 +297,33 @@ class GatewayView(View):
         except Exception as e:
             logger.info('---场景日志推送接口异常--- {}'.format(repr(e)))
             return response.json(500, repr(e))
+
+    @classmethod
+    def socket_msg_push(cls, request_dict, response):
+        """
+        智能插座开关状态推送
+        """
+        t = time.time()
+        logger = logging.getLogger('info')
+        try:
+            serial_number = request_dict.get('serialNumber', None)
+            status = request_dict.get('status', None)
+            if not all([serial_number, status]):
+                return response.json(444)
+            now_time = int(time.time())
+            LOGGER.info('已订阅成功接收:事件类型{},状态:{}'.format(serial_number, status))
+            # socket_info_qs = SocketInfo.objects.filter(serial_number=serial_number).values('device_id')
+            # if not socket_info_qs.exists():
+            #     return response.json(173)
+            # 获取主用户设备id
+            log_dict = {
+                'status': int(status),
+                'device_id': serial_number,
+                'created_time': now_time,
+            }
+            SceneLog.objects.create(**log_dict)
+            LOGGER.info(f'coast:{time.time() - t:.4f}s')
+            return response.json(0)
+        except Exception as e:
+            logger.info('---插座开关日志推送接口异常--- {}'.format(repr(e)))
+            return response.json(500, repr(e))

+ 2 - 12
Model/models.py

@@ -1,8 +1,6 @@
 from itertools import chain
 from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
 from django.db import models
-# from django.utils import six
-# from django.utils.encoding import python_2_unicode_compatible
 from six import python_2_unicode_compatible
 from imagekit.models import ProcessedImageField
 
@@ -66,10 +64,6 @@ class Permissions(models.Model):
     description = models.CharField(blank=True, null=True, max_length=128, verbose_name=u'描述信息', default='')
     objects = PermissionsManager()
 
-    def __str__(self):
-        return "%s" % (
-            six.text_type(self.description))
-
     class Meta:
         ordering = ['permName']
         db_table = 'permissions'
@@ -209,7 +203,6 @@ class Device_User(AbstractBaseUser):
 
     def get_all_permission(self):
         roles = self.role.all()
-        perms = self.permission.all()
 
         permslist = []
         for role in roles:
@@ -219,9 +212,6 @@ class Device_User(AbstractBaseUser):
                     permslist.append(perm.permName)
                 return permslist
 
-        for perm in perms:
-            permslist.append(perm.permName)
-
         permSet = set(permslist)
         for role in roles:
             permlist_tmp = []
@@ -1182,7 +1172,7 @@ class UidPushModel(models.Model):
     uid_set = models.ForeignKey(UidSetModel, to_field='id', on_delete=models.CASCADE)
     appBundleId = models.CharField(blank=True, max_length=32, verbose_name=u'appID')
     app_type = models.IntegerField(default=0, verbose_name=u'app类型 1:ios,2:安卓')
-    push_type = models.IntegerField(default=0, verbose_name=u'推送类型')  # 0,apns 1,安卓gcm 2,极光
+    push_type = models.IntegerField(default=0, verbose_name=u'推送类型')  # 0: apns, 1: 安卓gcm, 2: 极光, 3:华为, 4:小米, 5:vivo, 6:oppo, 7:魅族
     token_val = models.CharField(default='', max_length=160, verbose_name=u'设备验证令牌')
     m_code = models.CharField(default='', max_length=64, verbose_name='手机唯一标识')
     addTime = models.IntegerField(verbose_name='添加时间', default=0)
@@ -1202,7 +1192,7 @@ class GatewayPush(models.Model):
     user_id = models.CharField(default='', max_length=32, db_index=True, verbose_name=u'用户id')
     app_bundle_id = models.CharField(default='', max_length=32, verbose_name=u'app包id')
     app_type = models.IntegerField(default=0, verbose_name=u'app类型')  # 1: ios, 2: 安卓
-    push_type = models.IntegerField(default=0, verbose_name=u'推送类型')  # 0: apns, 1: 安卓gcm, 2: 极光
+    push_type = models.IntegerField(default=0, verbose_name=u'推送类型')  # 0: apns, 1: 安卓gcm, 2: 极光, 3:华为, 4:小米, 5:vivo, 6:oppo, 7:魅族
     token_val = models.CharField(default='', max_length=500, verbose_name=u'设备验证令牌')
     m_code = models.CharField(default='', max_length=64, db_index=True, verbose_name='手机唯一标识')
     lang = models.CharField(default='en', max_length=8, verbose_name='推送语言')

+ 12 - 42
Object/ETkObject.py

@@ -1,17 +1,3 @@
-#!/usr/bin/env python3  
-# -*- coding: utf-8 -*-  
-"""
-@Copyright (C) ansjer cop Video Technology Co.,Ltd.All rights reserved.
-@AUTHOR: ASJRD018
-@NAME: AnsjerFormal
-@software: PyCharm
-@DATE: 2019/6/1 17:25
-@Version: python3.6
-@MODIFY DECORD:ansjer dev
-@file: ETkObject.py
-@Contact: chanjunkai@163.com
-"""
-
 import base64
 import urllib.parse
 from random import Random
@@ -20,52 +6,36 @@ from random import Random
 class ETkObject(object):
     def __init__(self, etk):
         self.uid = ''
-        self.parseUid(etk)
+        self.decrypt_uid(etk)
 
-    def parseUid(self, etk):
+    def decrypt_uid(self, etk):
         try:
             c = base64.b64decode(etk)
             c = c[2:-2]
             c = urllib.parse.unquote(c.decode('utf-8'))
             c = base64.b64decode(c)
             uid = c.decode('utf-8')
-            if len(uid) == 20:
-                self.uid = uid
-                print(uid)
-            elif len(uid) == 14:
-                self.uid = uid
-                print(uid)
+            assert len(uid) == 20 or len(uid) == 14
+            self.uid = uid
         except Exception as e:
             print(repr(e))
 
-    def encrypt(self,data):
+    def encrypt_uid(self, data):
         s = data.encode()
         s = base64.b64encode(s)
-        startStr = self.randomParam()
-        endStr = self.randomParam()
-        s = '{startStr}{s}{endStr}'.format(startStr=startStr,s=s.decode('utf-8'),endStr=endStr)
+        startStr = self.generate_random()
+        endStr = self.generate_random()
+        s = '{startStr}{s}{endStr}'.format(startStr=startStr, s=s.decode('utf-8'), endStr=endStr)
         s = base64.b64encode(s.encode())
-        s=s.decode('utf-8')
+        s = s.decode('utf-8')
         return s
 
-    def randomParam(self):
-        characterSet = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsT' \
-                       'tUuVvWwXxYyZz0123456789'
+    @staticmethod
+    def generate_random():
+        characterSet = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
         length = len(characterSet) - 1
-
         random = Random()
         ss = ''
         for index in range(2):
             ss += characterSet[random.randint(0, length)]
         return ss
-
-
-# etkObj = ETkObject(etk='')
-# PP = etkObj.encrypt('HLK7EJ2VYLNHHUMG111A')
-# print('encode_data:')
-# print(PP)
-# print('decode_data:')
-# eobj = ETkObject(etk='T1dha3hDUkZOVk9UVTBOMDVVVWxOTlJqRXhNVUU9U2w=')
-# uid = eobj.uid
-# print(uid)
-

+ 20 - 12
Object/ResponseObject.py

@@ -6,7 +6,9 @@ class ResponseObject(object):
     def __init__(self, lang='en'):
         self.lang = lang
 
-    def data(self, code, res={}):
+    def data(self, code, res=None):
+        if res is None:
+            res = {}
         data_en = {
             0: 'Success',
             5: 'Please try again one minute later!',
@@ -18,16 +20,16 @@ class ResponseObject(object):
             48: 'System object error!',
             89: 'Already send the code, please check it or get it again after 10m',
             90: 'please check code or get it again after 1m',
-            99: 'Mail doesn\'t exist!',
+            99: 'Mail does not exist!',
             100: 'Phone format error!',
             101: 'Phone already existed!',
-            102: 'Phone doesn\'t exist!',
+            102: 'Phone does not exist!',
             103: 'Mail already existed!',
-            104: 'Account doesn\'t exist!',
+            104: 'Account does not exist!',
             105: 'Email format error!',
             107: 'The username not conform to the rules!',
             109: 'The password not conform to the rules!',
-            110: 'user doesn\'t activated',
+            110: 'user does not activated',
             111: 'Error password',
             119: 'The qr code has expired',
             120: 'The code has expired',
@@ -111,6 +113,7 @@ class ResponseObject(object):
             906: '文件操作错误',
             907: '文件不存在!',
         }
+
         if self.lang == 'cn':
             msg = data_cn
         elif self.lang == 'zh-Hans':
@@ -119,16 +122,21 @@ class ResponseObject(object):
             msg = data_cn
         else:
             msg = data_en
-        try:
-            message = msg[code]
-        except Exception as e:
-            message = '系统错误,code不存在'
-        return {'result_code': code, 'reason': message, 'result': res, 'error_code': code}
 
-    def formal(self, code, res={}):
+        reason = msg.get(code)
+        if reason is None:
+            reason = 'code不存在'
+
+        return {'result_code': code, 'reason': reason, 'result': res, 'error_code': code}
+
+    def formal(self, code, res=None):
+        if res is None:
+            res = {}
         formal_data = self.data(code, res)
         return json.dumps(formal_data, ensure_ascii=False)
 
-    def json(self, code, res={}):
+    def json(self, code, res=None):
+        if res is None:
+            res = {}
         result = self.formal(code, res)
         return HttpResponse(result)

+ 66 - 2
Service/CommonService.py

@@ -1,15 +1,18 @@
 # -*- coding: utf-8 -*-
 import datetime
+import os
 import time
+import hashlib
 from pathlib import Path
 from random import Random
 import ipdb
 import simplejson as json
+from boto3 import Session
 from django.core import serializers
 from django.utils import timezone
 from pyipip import IPIPDatabase
 
-from AnsjerPush.config import BASE_DIR
+from AnsjerPush.config import BASE_DIR, ACCESS_KEY_ID, SECRET_ACCESS_KEY, REGION_NAME, PUSH_BUCKET
 
 
 # 复用性且公用较高封装代码在这
@@ -217,4 +220,65 @@ class CommonService:
         file.write("uid:" + uid + "; " + "; tz:" + tz)
         file.write('\n')
         file.flush()
-        file.close()
+        file.close()
+
+    @classmethod
+    def upload_images(cls, file_dict, dir_path):
+        """
+        上传图片到S3,并删除本地图片
+        @param file_dict: S3图片路径
+        @param dir_path: 本地图片路径
+        @return: boolean
+        """
+        try:
+            s3 = Session(
+                aws_access_key_id=ACCESS_KEY_ID,
+                aws_secret_access_key=SECRET_ACCESS_KEY,
+                region_name=REGION_NAME
+            ).resource('s3')
+
+            for file_path, upload_path in file_dict.items():
+                upload_data = open(file_path, 'rb')
+                s3.Bucket(PUSH_BUCKET).put_object(Key=upload_path, Body=upload_data)
+
+            # 删除图片
+            cls.del_path(dir_path)
+            cls.del_path(dir_path + '.jpg')
+            return True
+        except Exception as e:
+            print(repr(e))
+            return False
+
+    @classmethod
+    def del_path(cls, path):
+        """
+        删除目录文件
+        @param path: 文件路径
+        @return: None
+        """
+        if not os.path.exists(path):
+            return
+        if os.path.isfile(path):
+            os.remove(path)
+        else:
+            items = os.listdir(path)
+            for f in items:
+                c_path = os.path.join(path, f)
+                if os.path.isdir(c_path):
+                    cls.del_path(c_path)
+                else:
+                    os.remove(c_path)
+            os.rmdir(path)
+
+    @staticmethod
+    def getMD5Sign(data, key):
+        '''
+        魅族MD5签名
+        '''
+        dataList = []
+        for k in sorted(data):
+            dataList.append("%s=%s" % (k, data[k]))
+        data = (''.join(dataList))
+        data = data + key
+        sign = hashlib.md5(data.encode(encoding="utf-8")).hexdigest()
+        return sign

+ 542 - 0
Service/DevicePushService.py

@@ -0,0 +1,542 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : DevicePushService.py
+@Time    : 2022/11/23 11:40
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import hashlib
+import json
+import logging
+import os
+import threading
+import time
+
+import apns2
+import jpush as jpush
+import requests
+from pyfcm import FCMNotification
+
+from AnsjerPush.config import JPUSH_CONFIG, FCM_CONFIG, APNS_CONFIG, BASE_DIR, APNS_MODE, APP_BUNDLE_DICT, \
+    XMPUSH_CONFIG, VIVOPUSH_CONFIG, OPPOPUSH_CONFIG, MEIZUPUSH_CONFIG
+from AnsjerPush.config import SERVER_TYPE
+from Model.models import UidPushModel, SysMsgModel
+from Object.ETkObject import ETkObject
+from Object.RedisObject import RedisObject
+from Object.UidTokenObject import UidTokenObject
+from Object.utils import LocalDateTimeUtil
+from Service.CommonService import CommonService
+from Service.EquipmentInfoService import EquipmentInfoService
+from Service.HuaweiPushService.HuaweiPushService import HuaweiPushObject
+from Service.PushService import PushObject
+from Service.VivoPushService.push_admin.APIMessage import PushMessage
+from Service.VivoPushService.push_admin.APISender import APISender
+
+LOGGING = logging.getLogger('info')
+
+
+class DevicePushService:
+
+    @staticmethod
+    def decode_uid(etk, uidToken):
+        """
+        解密UID,优先解密etk 否则判断uidToken
+        """
+        # 解密获取uid
+        if etk:
+            eto = ETkObject(etk)
+            uid = eto.uid
+        else:
+            uto = UidTokenObject(uidToken)
+            uid = uto.UID
+        LOGGING.info('消息推送-当前UID:{}'.format(uid))
+        return uid
+
+    @classmethod
+    def query_uid_push(cls, uid):
+        """
+        查询uid_set与push数据列表
+        """
+        uid_push_qs = UidPushModel.objects.filter(uid_set__uid=uid, uid_set__detect_status=1). \
+            values('token_val', 'app_type', 'appBundleId', 'm_code', 'push_type', 'userID_id', 'userID__NickName',
+                   'lang', 'm_code', 'tz', 'uid_set__nickname', 'uid_set__detect_interval', 'uid_set__detect_group',
+                   'uid_set__channel', 'uid_set__ai_type', 'uid_set__new_detect_interval')
+        return uid_push_qs
+
+    @staticmethod
+    def cache_uid_push(uid_push_qs):
+        """
+        将uid_push 信息进行缓存
+        @param uid_push_qs: uid_set & uid_push 列表对象
+        @return: uid_set_list
+        """
+        uid_set_list = []
+        for qs in uid_push_qs:
+            uid_set_list.append(qs)
+        # redis_obj.set_data(key=name, val=str(redis_list), expire=expire)
+        return uid_set_list
+
+    @staticmethod
+    def cache_push_detect_interval(redis_obj, name, detect_interval, new_detect_interval):
+        """
+        缓存设置推送消息的时间间隔
+        @param redis_obj: redis对象
+        @param name: redis key
+        @param detect_interval: 原推送时间间隔
+        @param new_detect_interval: 新推送时间间隔
+        """
+        if SERVER_TYPE != 'Ansjer.cn_config.cn_formal_settings':
+            detect_interval = new_detect_interval if new_detect_interval > 0 else detect_interval
+            detect_interval = 60 if detect_interval < 60 else detect_interval
+        redis_obj.set_data(key=name, val=1, expire=detect_interval - 5)
+        LOGGING.info('消息推送-缓存设置APP推送间隔:{}s'.format(detect_interval))
+
+    @classmethod
+    def save_msg_push(cls, uid_set_push_list, **params):
+        """
+        APP消息推送以及报警消息存库
+        @nickname 设备名称
+        @channel 通道
+        @event_type 事件类型
+        """
+        new_device_info_list = []
+        sys_msg_list = []
+        userID_ids = []
+        kwag_args = params['kwag_args']
+        code_data = {'do_apns_code': '', 'do_fcm_code': '', 'do_jpush_code': ''}
+        local_date_time = ''
+        push_permission = True
+        for up in uid_set_push_list:
+            appBundleId = up['appBundleId']
+            token_val = up['token_val']
+            lang = up['lang']
+            tz = up['tz']
+            if tz is None or tz == '':
+                tz = 0
+            # 发送标题
+            msg_title = cls.get_msg_title(nickname=params['nickname'])
+            # 发送内容
+            msg_text = cls.get_msg_text(channel=params['channel'], n_time=params['n_time'], lang=lang,
+                                        tz=tz, event_type=params['event_type'],
+                                        electricity=params['electricity'])
+            kwag_args['appBundleId'] = appBundleId
+            kwag_args['token_val'] = token_val
+            kwag_args['msg_title'] = msg_title
+            kwag_args['msg_text'] = msg_text
+            LOGGING.info('推送要的数据: {}'.format(kwag_args))
+            local_date_time = CommonService.get_now_time_str(n_time=params['n_time'], tz=tz, lang='cn')
+            LOGGING.info('<<<<<根据时区计算后日期={},时区={}'.format(local_date_time, tz))
+            local_date_time = local_date_time[0:10]
+            LOGGING.info('<<<<<切片后的日期={}'.format(local_date_time))
+            # 以下是存库
+            userID_id = up["userID_id"]
+            if userID_id not in userID_ids:
+                now_time = int(time.time())
+                if params['is_sys_msg']:
+                    sys_msg_text = cls.get_msg_text(channel=params['channel'], n_time=params['n_time'], lang=lang,
+                                                    tz=tz,
+                                                    event_type=params['event_type'], electricity=params['electricity'],
+                                                    is_sys=1)
+                    sys_msg_list.append(SysMsgModel(userID_id=userID_id, msg=sys_msg_text, addTime=now_time,
+                                                    updTime=now_time, uid=params['uid'],
+                                                    eventType=params['event_type']))
+                else:
+                    LOGGING.info('分表存数据start------')
+                    params['userID_id'] = userID_id
+                    push_permission = DevicePushService.check_share_permission(userID_id, params['channel'],
+                                                                               params['uid'])
+                    if push_permission:
+                        new_device_info_list.append(cls.created_device_vo(local_date_time, **params))
+                userID_ids.append(userID_id)
+            params['appBundleId'] = appBundleId
+            params['token_val'] = token_val
+            params['lang'] = lang
+            params['tz'] = tz
+            params['kwag_args'] = kwag_args
+            code_data = cls.send_app_msg_push(up['push_type'], **params) if push_permission else code_data
+        return {'code_date': code_data, 'new_device_info_list': new_device_info_list, 'sys_msg_list': sys_msg_list,
+                'local_date_time': local_date_time}
+
+    @classmethod
+    def send_app_msg_push(cls, push_type, **param):
+        """
+        发送app消息推送
+        """
+        try:
+            kwargs = param['kwag_args']
+            result = {'do_apns_code': '', 'do_fcm_code': '', 'do_jpush_code': '', 'do_xmpush_code': '',
+                      'do_vivopush_code': '', 'do_meizupush_code': '', 'do_oppopush_code': ''}
+            # 判断是否进行APP消息推送,如app_push不为空,则不进行推送
+            if not param['app_push']:
+                LOGGING.info('APP准备推送:{}, {}'.format(param['uid'], param))
+                # 推送显示图片
+                if (param['is_st'] == 1 or param['is_st'] == 3) and \
+                        (push_type == 0 or push_type == 1 or push_type == 3):
+                    if param['is_st'] == 1:
+                        key = '{}/{}/{}.jpeg'.format(param['uid'], param['channel'], param['n_time'])
+                    else:
+                        key = '{}/{}/{}_0.jpeg'.format(param['uid'], param['channel'], param['n_time'])
+                    push_thread = threading.Thread(target=cls.async_send_picture_push, args=(
+                        push_type, param['aws_s3_client'], param['bucket'], key, param['uid'], param['appBundleId'],
+                        param['token_val'], param['event_type'], param['n_time'],
+                        param['kwag_args']['msg_title'], param['kwag_args']['msg_text'], param['channel']))
+                    push_thread.start()
+                else:
+                    if push_type == 0:  # ios apns
+                        result['do_apns_code'] = cls.do_apns(**kwargs)
+                    elif push_type == 1:  # android gcm
+                        result['do_fcm_code'] = cls.do_fcm(**kwargs)
+                    elif push_type == 2:  # android jpush
+                        result['do_jpush_code'] = cls.do_jpush(**kwargs)
+                    elif push_type == 3:
+                        huawei_push_object = HuaweiPushObject()
+                        huawei_push_object.send_push_notify_message(**kwargs)
+                    elif push_type == 4:  # android xmpush
+                        result['do_xmpush_code'] = cls.do_xmpush(**kwargs)
+                    elif push_type == 5:  # android vivopush
+                        result['do_vivopush_code'] = PushObject.android_vivopush(**kwargs)
+                    elif push_type == 6:  # android oppopush
+                        result['do_oppopush_code'] = cls.do_oppopush(**kwargs)
+                    elif push_type == 7:  # android meizupush
+                        result['do_meizupush_code'] = PushObject.android_meizupush(**kwargs)
+            return result
+        except Exception as e:
+            LOGGING.info('异常详情,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+            return None
+
+    @classmethod
+    def save_sys_msg(cls, is_sys_msg, local_date_time, sys_msg_list, new_device_info_list):
+        """
+        保存系统消息&设备推送消息存库
+        """
+        if is_sys_msg:
+            SysMsgModel.objects.bulk_create(sys_msg_list)
+        else:
+            # new 分表批量存储 设备信息
+            if new_device_info_list and len(new_device_info_list) > 0:
+                # 根据日期获得星期几
+                week = LocalDateTimeUtil.date_to_week(local_date_time)
+                EquipmentInfoService.equipment_info_bulk_create(week, new_device_info_list)
+                LOGGING.info('设备信息分表批量保存end------')
+        return True
+
+    @classmethod
+    def created_device_vo(cls, local_date_time, **params):
+        """
+        获取设备推送表对象
+        """
+        return EquipmentInfoService.get_equipment_info_obj(
+            local_date_time,
+            device_user_id=params['userID_id'],
+            event_time=params['n_time'],
+            event_type=params['event_type'],
+            device_uid=params['uid'],
+            device_nick_name=params['nickname'],
+            channel=params['channel'],
+            alarm='Motion \tChannel:{channel}'.format(channel=params['channel']),
+            is_st=params['is_st'],
+            receive_time=params['n_time'],
+            add_time=int(time.time()),
+            storage_location=2,
+            border_coords='',
+        )
+
+    @staticmethod
+    def get_msg_title(nickname):
+        """
+        获取消息标题
+        """""
+        return nickname
+
+    @staticmethod
+    def get_msg_text(channel, n_time, lang, tz, event_type, electricity='', is_sys=0):
+        """
+        获取消息文本
+        """
+        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
+        etype = int(event_type)
+        if lang == 'cn':
+            if etype == 704:
+                msg_type = '剩余电量 ' + electricity
+            elif etype == 702:
+                msg_type = '摄像头休眠'
+            elif etype == 703:
+                msg_type = '摄像头唤醒'
+            else:
+                msg_type = ''
+            if is_sys:
+                send_text = '{msg_type} 通道:{channel}'.format(msg_type=msg_type, channel=channel)
+            else:
+                send_text = '{msg_type} 通道:{channel} 日期:{date}'.format(msg_type=msg_type, channel=channel, date=n_date)
+        else:
+            if etype == 704:
+                msg_type = 'Battery remaining ' + electricity
+            elif etype == 702:
+                msg_type = 'Camera sleep'
+            elif etype == 703:
+                msg_type = 'Camera wake'
+            else:
+                msg_type = ''
+            if is_sys:
+                send_text = '{msg_type} channel:{channel}'. \
+                    format(msg_type=msg_type, channel=channel)
+            else:
+                send_text = '{msg_type} channel:{channel} date:{date}'. \
+                    format(msg_type=msg_type, channel=channel, date=n_date)
+        return send_text
+
+    @staticmethod
+    def do_jpush(uid, channel, appBundleId, token_val, event_type, n_time,
+                 msg_title, msg_text):
+        """
+        android 国内极光APP消息提醒推送
+        """
+        app_key = JPUSH_CONFIG[appBundleId]['Key']
+        master_secret = JPUSH_CONFIG[appBundleId]['Secret']
+        _jpush = jpush.JPush(app_key, master_secret)
+        push = _jpush.create_push()
+        push.audience = jpush.registration_id(token_val)
+        push_data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
+                     "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
+        android = jpush.android(alert=msg_text, priority=1, style=1, alert_type=7,
+                                big_text=msg_text, title=msg_title,
+                                extras=push_data)
+        push.notification = jpush.notification(android=android)
+        push.platform = jpush.all_
+        res = push.send()
+        print(res)
+        return res.status_code
+
+    @staticmethod
+    def do_fcm(uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
+        """
+        android 谷歌APP消息提醒推送
+        """
+        try:
+            serverKey = FCM_CONFIG[appBundleId]
+        except Exception as e:
+            LOGGING.info('异常详情,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+            return 'serverKey abnormal'
+        push_service = FCMNotification(api_key=serverKey)
+        data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
+                "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
+        result = push_service.notify_single_device(registration_id=token_val, message_title=msg_title,
+                                                   message_body=msg_text, data_message=data,
+                                                   extra_kwargs={
+                                                       'default_vibrate_timings': True,
+                                                       'default_sound': True,
+                                                       'default_light_settings': True
+                                                   })
+        return result
+
+    @staticmethod
+    def do_apns(uid, channel, appBundleId, token_val, event_type, n_time, msg_title,
+                msg_text):
+        """
+        ios 消息提醒推送
+        """
+        LOGGING.info("进来do_apns函数了")
+        LOGGING.info(token_val)
+        LOGGING.info(APNS_MODE)
+        LOGGING.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']))
+
+            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)
+            payload = apns2.Payload(alert=alert, custom=push_data, sound="default")
+
+            # return uid, channel, appBundleId, str(token_val), event_type, n_time, msg_title,msg_text
+            n = apns2.Notification(payload=payload, priority=apns2.PRIORITY_LOW)
+            res = cli.push(n=n, device_token=token_val, topic=appBundleId)
+            print(res.status_code)
+            LOGGING.info("apns_推送状态:")
+            LOGGING.info(res.status_code)
+
+            if res.status_code == 200:
+                return res.status_code
+            else:
+                print('apns push fail')
+                print(res.reason)
+                LOGGING.info('apns push fail')
+                LOGGING.info(res.reason)
+                return res.status_code
+        except (ValueError, ArithmeticError):
+            return 'The program has a numeric format exception, one of the arithmetic exceptions'
+        except Exception as e:
+            print(repr(e))
+            print('do_apns函数错误行号', e.__traceback__.tb_lineno)
+            LOGGING.info('异常详情,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+            return repr(e)
+
+    @staticmethod
+    def do_xmpush(uid, channel, appBundleId, token_val, event_type, n_time,
+                  msg_title, msg_text):
+        """
+        android 国内小米APP消息提醒推送
+        """
+        url = 'https://api.xmpush.xiaomi.com/v3/message/regid'
+        app_secret = XMPUSH_CONFIG[appBundleId]
+        payload = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1',
+                   'received_at': n_time, 'event_time': n_time, 'event_type': event_type,
+                   'uid': uid, 'channel': channel
+                   }
+        data = {
+            'title': msg_title,
+            'description': msg_text,
+            'payload': 'payload',
+            'restricted_package_name': appBundleId,
+            'registration_id': token_val,
+        }
+
+        headers = {
+            'Authorization': 'key={}'.format(app_secret)
+        }
+        response = requests.post(url, data=data, headers=headers)
+        if response.status_code == 200:
+            return response.json()
+
+    @classmethod
+    def do_oppopush(cls, uid, channel, appBundleId, token_val, event_type, n_time,
+                    msg_title, msg_text):
+        """
+        android 国内oppo APP消息提醒推送
+        """
+        app_key = OPPOPUSH_CONFIG[appBundleId]['Key']
+        master_secret = OPPOPUSH_CONFIG[appBundleId]['Secret']
+        url = 'https://api.push.oppomobile.com/'
+        now_time = str(round(time.time() * 1000))
+        # 1、实例化一个sha256对象
+        sha256 = hashlib.sha256()
+        # 2、调用update方法进行加密
+        sha256.update((app_key + now_time + master_secret).encode('utf-8'))
+        # 3、调用hexdigest方法,获取加密结果
+        sign = sha256.hexdigest()
+        # 获取auth_token
+        get_token_url = url + 'server/v1/auth'
+        post_data = {
+            'app_key': app_key,
+            'sign': sign,
+            'timestamp': now_time
+        }
+        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+        response = requests.post(get_token_url, data=post_data, headers=headers)
+        result = response.json()
+        # 发送推送
+        push_url = url + 'server/v1/message/notification/unicast'
+        extra_data = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1',
+                      'received_at': n_time, 'event_time': n_time, 'event_type': event_type,
+                      'uid': uid, 'channel': channel}
+        message = {
+            "target_type": 2,
+            "target_value": token_val,
+            "notification": {
+                "title": msg_title,
+                "content": msg_text,
+            }
+        }
+        push_data = {
+            'auth_token': result['data']['auth_token'],
+            'message': json.dumps(message)
+        }
+
+        response = requests.post(push_url, data=push_data, headers=headers)
+        if response.status_code == 200:
+            return response.json()
+
+    @classmethod
+    def async_send_picture_push(cls, push_type, aws_s3_client, bucket, key, uid, appBundleId,
+                                token_val, event_type, n_time, msg_title, msg_text, channel):
+        """
+        异步APP图片推送
+        """
+        try:
+            image_url = aws_s3_client.generate_presigned_url('get_object',
+                                                             Params={'Bucket': bucket, 'Key': key},
+                                                             ExpiresIn=3600)
+            LOGGING.info('推送图片url:{}'.format(image_url))
+            if push_type == 0:
+                PushObject.ios_apns_push(uid, appBundleId, token_val, n_time, event_type, msg_title, msg_text,
+                                         uid, channel, image_url)
+            elif push_type == 1:
+                PushObject.android_fcm_push(uid, appBundleId, token_val, n_time, event_type, msg_title,
+                                            msg_text, uid, channel, image_url)
+            elif push_type == 3:
+                huawei_push_object = HuaweiPushObject()
+                huawei_push_object.send_push_notify_message(token_val=token_val, msg_title=msg_title, msg_text=msg_text,
+                                                            image_url=image_url)
+
+        except Exception as e:
+            LOGGING.info('异常详情,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    @staticmethod
+    def get_push_url(**params):
+        """
+        获取推送URL,设备根本当前返回结果进行数据上传
+        @return: re_data
+        """
+        re_data = {'code': 0, 'msg': 'success'}
+        if params['is_st'] == 0 or params['is_st'] == 2:
+            re_data['msg'] = 'success 0 or 2'
+            for up in params['uid_set_push_list']:
+                if up['push_type'] == 0:  # ios apns
+                    up['do_apns_code'] = params['code_dict']['code_date']['do_apns_code']
+                elif up['push_type'] == 1:  # android gcm
+                    up['do_fcm_code'] = params['code_dict']['code_date']['do_fcm_code']
+                elif up['push_type'] == 2:  # android jpush
+                    up['do_jpush_code'] = params['code_dict']['code_date']['do_jpush_code']
+                elif up['push_type'] == 4:  # android jpush
+                    up['do_xmpush_code'] = params['code_dict']['code_date']['do_xmpush_code']
+                elif up['push_type'] == 5:  # android jpush
+                    up['do_vivopush_code'] = params['code_dict']['code_date']['do_vivopush_code']
+                elif up['push_type'] == 7:  # android jpush
+                    up['do_meizupush_code'] = params['code_dict']['code_date']['do_meizupush_code']
+                del up['push_type']
+                del up['userID_id']
+                del up['userID__NickName']
+                del up['lang']
+                del up['tz']
+                del up['uid_set__nickname']
+                del up['uid_set__detect_interval']
+                del up['uid_set__detect_group']
+            re_data['re_list'] = params['uid_set_push_list']
+        elif params['is_st'] == 1:
+            key_name = '{uid}/{channel}/{filename}.jpeg' \
+                .format(uid=params['uid'], channel=params['channel'], filename=params['n_time'])
+            re_args = {'Key': key_name}
+            if params['region'] == 2:  # 2:国内
+                re_args['Bucket'] = 'push'
+            else:  # 1:国外
+                re_args['Bucket'] = 'foreignpush'
+            response_url = DevicePushService.generate_s3_url(params['aws_s3_client'], re_args)
+            re_data['img_push'] = response_url
+        elif params['is_st'] == 3:
+            img_url_list = []
+            if params['region'] == 2:  # 2:国内
+                re_args = {'Bucket': 'push'}
+            else:  # 1:国外
+                re_args = {'Bucket': 'foreignpush'}
+            for i in range(params['is_st']):
+                key_name = '{uid}/{channel}/{filename}_{st}.jpeg'. \
+                    format(uid=params['uid'], channel=params['channel'], filename=params['n_time'], st=i)
+                re_args['Key'] = key_name
+                response_url = DevicePushService.generate_s3_url(params['aws_s3_client'], re_args)
+                img_url_list.append(response_url)
+            re_data['img_url_list'] = img_url_list
+            re_data['msg'] = 'success 3'
+        return re_data
+
+    @staticmethod
+    def generate_s3_url(aws_s3_client, params):
+        """
+        获取S3对象URL
+        """
+        response_url = aws_s3_client.generate_presigned_url(
+            ClientMethod='put_object',
+            Params=params,
+            ExpiresIn=3600
+        )
+        return response_url

+ 0 - 107
Service/GatewayService.py

@@ -1,107 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@Time : 2022/5/19 11:43
-@Auth : Locky
-@File :GatewayService.py
-@IDE :PyCharm
-"""
-import logging
-import os
-
-import apns2
-import jpush
-from pyfcm import FCMNotification
-
-from AnsjerPush.config import APP_BUNDLE_DICT, APNS_MODE, BASE_DIR, APNS_CONFIG, FCM_CONFIG, JPUSH_CONFIG
-# 网关推送类
-from Service.CommonService import CommonService
-
-
-class GatewayPushService:
-    # 获取推送消息标题
-    @staticmethod
-    def get_msg_title(app_bundle_id, nickname):
-        if app_bundle_id in APP_BUNDLE_DICT.keys():
-            return APP_BUNDLE_DICT[app_bundle_id] + '(' + nickname + ')'
-        else:
-            return nickname
-
-    # 获取推送消息内容
-    @staticmethod
-    def get_msg_text(n_time, tz, lang, alarm):
-        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
-        if lang == 'cn':
-            msg_text = '{} 日期:{}'.format(alarm, n_date)
-        else:
-            msg_text = '{} date:{}'.format(alarm, n_date)
-        return msg_text
-
-    # ios apns 推送
-    @staticmethod
-    def ios_apns_push(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text,
-                      uid='', channel='1', launch_image=None):
-        logger = logging.getLogger('info')
-        try:
-            pem_path = os.path.join(BASE_DIR, APNS_CONFIG[app_bundle_id]['pem_path'])
-            logger.info('apns推送app_bundle_id:{}, pem_path:{}'.format(app_bundle_id, pem_path))
-            cli = apns2.APNSClient(mode=APNS_MODE, client_cert=pem_path)
-            alert = apns2.PayloadAlert(title=msg_title, body=msg_text, launch_image=launch_image)
-            push_data = {'alert': 'Motion', 'msg': '', 'sound': '', 'zpush': '1', 'uid': uid, 'channel': channel,
-                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname,
-                         'image_url': launch_image
-                         }
-            payload = apns2.Payload(alert=alert, custom=push_data, sound='default', category='myCategory',
-                                    mutable_content=True)
-            n = apns2.Notification(payload=payload, priority=apns2.PRIORITY_LOW)
-            res = cli.push(n=n, device_token=token_val, topic=app_bundle_id)
-            assert res.status_code == 200
-        except Exception as e:
-            logger.info('--->IOS推送异常{}'.format(repr(e)))
-            return repr(e)
-
-    # android fcm 推送
-    @staticmethod
-    def android_fcm_push(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text,
-                         uid='', channel='1', image=''):
-        logger = logging.getLogger('info')
-        try:
-            serverKey = FCM_CONFIG[app_bundle_id]
-            push_service = FCMNotification(api_key=serverKey)
-            push_data = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1', 'image': image,
-                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname,
-                         'uid': uid, 'channel': channel
-                         }
-            result = push_service.notify_single_device(registration_id=token_val, message_title=msg_title,
-                                                       message_body=msg_text, data_message=push_data,
-                                                       extra_kwargs={'default_sound': True,
-                                                                     'default_vibrate_timings': True,
-                                                                     'default_light_settings': True,
-                                                                     }
-                                                       )
-            logger.info('fcm推送结果:{}'.format(result))
-            return result
-        except Exception as e:
-            return repr(e)
-
-    # android 极光 推送
-    @staticmethod
-    def android_jpush(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text):
-        try:
-            app_key = JPUSH_CONFIG[app_bundle_id]['Key']
-            master_secret = JPUSH_CONFIG[app_bundle_id]['Secret']
-            # 换成各自的app_key和master_secret
-            _jpush = jpush.JPush(app_key, master_secret)
-            push = _jpush.create_push()
-            push.audience = jpush.registration_id(token_val)
-            push_data = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1',
-                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname
-                         }
-            android = jpush.android(title=msg_title, big_text=msg_text, alert=msg_text, extras=push_data,
-                                    priority=1, style=1, alert_type=7
-                                    )
-            push.notification = jpush.notification(android=android)
-            push.platform = jpush.all_
-            res = push.send()
-            assert res.status_code == 200
-        except Exception as e:
-            return repr(e)

+ 97 - 0
Service/HuaweiPushService/HuaweiPushService.py

@@ -0,0 +1,97 @@
+import json
+import logging
+
+from Service.HuaweiPushService import push_admin
+from Service.HuaweiPushService.push_admin import messaging
+
+
+class HuaweiPushObject:
+    # 华为推送服务类
+
+    def __init__(self):
+        self.app_id = '101064781'
+        self.app_secret = '29d5c5367208e35079f14779597b8f6bcc28ee39091546ed577862231fdd0fdd'
+        self.init_app()
+
+    def init_app(self):
+        """init sdk app"""
+        push_admin.initialize_app(self.app_id, self.app_secret)
+
+    def send_push_notify_message(self, token_val, msg_title, msg_text, image_url=None):
+        """
+        发送推送消息
+        @param token_val: 手机推送token
+        @param msg_title: 标题
+        @param msg_text: 内容
+        @param image_url: 图片链接
+        @return:
+        """
+        logger = logging.getLogger('info')
+        logger.info('华为推送参数:{}, {}, {}, {}'.format(token_val, msg_title, msg_text, image_url))
+
+        msg_title = '设备昵称: {}'.format(msg_title)
+        notification = messaging.Notification(
+            title=msg_title,
+            body=msg_text,
+            image=image_url
+        )
+        # 推送通知内容配置
+        android_notification = self.android_notification(msg_title, msg_text)
+        # 安卓配置
+        android = messaging.AndroidConfig(
+            collapse_key=-1,
+            urgency=messaging.AndroidConfig.NORMAL_PRIORITY,
+            ttl='10000s',
+            bi_tag='the_sample_bi_tag_for_receipt_service',
+            notification=android_notification,
+            category='DEVICE_REMINDER'
+        )
+
+        message = messaging.Message(
+            notification=notification,
+            android=android,
+            token=[token_val]
+        )
+
+        try:
+            # Case 1: Local CA sample code
+            # response = messaging.send_message(message, verify_peer='../Push-CA-Root.pem')
+            # Case 2: No verification of HTTPS's certificate
+            # response = messaging.send_message(message)
+            # Case 3: use certifi Library
+            import certifi
+            response = messaging.send_message(message, verify_peer=certifi.where())
+            logger.info('华为推送响应: {}'.format(json.dumps(vars(response))))
+            assert (response.code == '80000000')
+        except Exception as e:
+            logger.info('华为推送异常: {}'.format(repr(e)))
+
+    @staticmethod
+    def android_notification(msg_title, msg_text):
+        return messaging.AndroidNotification(
+            icon='/raw/ic_launcher2',
+            color='#AACCDD',
+            sound='/raw/shake',
+            default_sound=True,
+            click_action=messaging.AndroidClickAction(
+                action_type=1,
+                intent='intent://com.huawei.codelabpush/deeplink?#Intent;scheme=pushscheme;launchFlags=0x4000000;i.age=180;S.name=abc;end'),
+            body_loc_key='M.String.body',
+            body_loc_args=('boy', 'dog'),
+            title_loc_key='M.String.title',
+            title_loc_args=['Girl', 'Cat'],
+            channel_id='1',
+            notify_summary='',
+            multi_lang_key={'title_key': {'en': 'value1'}, 'body_key': {'en': 'value2'}},
+            style=1,
+            big_title=msg_title,
+            big_body=msg_text,
+            auto_clear=86400000,
+            importance=messaging.AndroidNotification.PRIORITY_HIGH,
+            light_settings=messaging.AndroidLightSettings(color=messaging.AndroidLightSettingsColor(
+                alpha=0, red=0, green=1, blue=1), light_on_duration='3.5', light_off_duration='5S'),
+            badge=messaging.AndroidBadgeNotification(
+                add_num=1, clazz='Classic'),
+            visibility=messaging.AndroidNotification.PUBLIC,
+            foreground_show=True
+        )

+ 71 - 0
Service/HuaweiPushService/push_admin/__init__.py

@@ -0,0 +1,71 @@
+# -*-coding:utf-8-*-
+#
+# Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Huawei Admin SDK for Python."""
+
+import threading
+from Service.HuaweiPushService.push_admin import _app
+
+_apps = {}
+_apps_lock = threading.RLock()
+_DEFAULT_APP_NAME = 'DEFAULT'
+
+
+def initialize_app(appid_at, appsecret_at, appid_push=None, token_server='https://oauth-login.cloud.huawei.com/oauth2/v3/token',
+                   push_open_url='https://push-api.cloud.huawei.com'):
+    """
+        Initializes and returns a new App instance.
+        :param appid_at: appid parameters obtained by developer alliance applying for Push service
+        :param appsecret_at: appsecret parameters obtained by developer alliance applying for Push service
+        :param appid_push: the application Id in the URL
+        :param token_server: Oauth server URL
+        :param push_open_url: push open API URL
+    """
+    app = _app.App(appid_at, appsecret_at, appid_push, token_server=token_server, push_open_url=push_open_url)
+
+    with _apps_lock:
+        if appid_at not in _apps:
+            _apps[appid_at] = app
+
+        """set default app instance"""
+        if _apps.get(_DEFAULT_APP_NAME) is None:
+            _apps[_DEFAULT_APP_NAME] = app
+
+
+def get_app(appid=None):
+    """
+        get app instance
+        :param appid: appid parameters obtained by developer alliance applying for Push service
+        :return: app instance
+        Raise: ValueError
+    """
+    if appid is None:
+        with _apps_lock:
+            app = _apps.get(_DEFAULT_APP_NAME)
+            if app is None:
+                raise ValueError('The default Huawei app is not exists. '
+                                 'This means you need to call initialize_app() it.')
+            return app
+
+    with _apps_lock:
+        if appid not in _apps:
+            raise ValueError('Huawei app id[{0}] is not exists. '
+                             'This means you need to call initialize_app() it.'.format(appid))
+
+        app = _apps.get(appid)
+        if app is None:
+            raise ValueError('The app id[{0}] is None.'.format(appid))
+        return app

+ 190 - 0
Service/HuaweiPushService/push_admin/_app.py

@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import time
+import urllib
+import urllib.parse
+
+from Service.HuaweiPushService.push_admin import _http
+from Service.HuaweiPushService.push_admin import _message_serializer
+
+
+class App(object):
+    """application for HW Cloud Message(HCM)"""
+
+    JSON_ENCODER = _message_serializer.MessageSerializer()
+
+    @classmethod
+    def _send_to_server(cls, headers, body, url, verify_peer=False):
+        try:
+            msg_body = json.dumps(body)
+            response = _http.post(url, msg_body, headers, verify_peer)
+
+            if response.status_code is not 200:
+                raise ApiCallError('http status code is {0} in send.'.format(response.status_code))
+
+            # json text to dict
+            resp_dict = json.loads(response.text)
+            return resp_dict
+
+        except Exception as e:
+            raise ApiCallError('caught exception when send. {0}'.format(e))
+
+    def __init__(self, appid_at, app_secret_at, appid_push, token_server='https://oauth-login.cloud.huawei.com/oauth2/v3/token',
+                 push_open_url='https://push-api.cloud.huawei.com'):
+        """class init"""
+        self.app_id_at = appid_at
+        self.app_secret_at = app_secret_at
+        if appid_push is None:
+            self.appid_push = appid_at
+        else:
+            self.appid_push = appid_push
+        self.token_expired_time = 0
+        self.access_token = None
+        self.token_server = token_server
+        self.push_open_url = push_open_url
+        self.hw_push_server = self.push_open_url + "/v1/{0}/messages:send"
+        self.hw_push_topic_sub_server = self.push_open_url + "/v1/{0}/topic:subscribe"
+        self.hw_push_topic_unsub_server = self.push_open_url + "/v1/{0}/topic:unsubscribe"
+        self.hw_push_topic_query_server = self.push_open_url + "/v1/{0}/topic:list"
+
+    def _refresh_token(self, verify_peer=False):
+        """refresh access token
+        :param verify_peer: (optional) Either a boolean, in which case it controls whether we verify
+            the server's TLS certificate, or a string, in which case it must be a path
+            to a CA bundle to use. Defaults to ``True``.
+        """
+        headers = dict()
+        headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'
+
+        params = dict()
+        params['grant_type'] = 'client_credentials'
+        params['client_secret'] = self.app_secret_at
+        params['client_id'] = self.app_id_at
+
+        msg_body = urllib.parse.urlencode(params)
+
+        try:
+            response = _http.post(self.token_server, msg_body, headers, verify_peer=verify_peer)
+
+            if response.status_code is not 200:
+                return False, 'http status code is {0} in get access token'.format(response.status_code)
+
+            """ json string to directory """
+            response_body = json.loads(response.text)
+
+            self.access_token = response_body.get('access_token')
+            self.token_expired_time = int(round(time.time() * 1000)) + (int(response_body.get('expires_in')) - 5 * 60) * 1000
+
+            return True, None
+        except Exception as e:
+            raise ApiCallError(format(repr(e)))
+
+    def _is_token_expired(self):
+        """is access token expired"""
+        if self.access_token is None:
+            """ need refresh token """
+            return True
+        return int(round(time.time() * 1000)) >= self.token_expired_time
+
+    def _update_token(self, verify_peer=False):
+        """
+        :param verify_peer: (optional) Either a boolean, in which case it controls whether we verify
+            the server's TLS certificate, or a string, in which case it must be a path
+            to a CA bundle to use. Defaults to ``True``.
+        :return:
+        """
+        if self._is_token_expired() is True:
+            result, reason = self._refresh_token(verify_peer)
+            if result is False:
+                raise ApiCallError(reason)
+
+    def _create_header(self):
+        headers = dict()
+        headers['Content-Type'] = 'application/json;charset=utf-8'
+        headers['Authorization'] = 'Bearer {0}'.format(self.access_token)
+        return headers
+
+    def send(self, message, validate_only, **kwargs):
+        """
+            Sends the given message Huawei Cloud Messaging (HCM)
+            :param message: JSON format message
+            :param validate_only: validate message format or not
+            :param kwargs:
+                   verify_peer: HTTPS server identity verification, use library 'certifi'
+            :return:
+                response dict: response body dict
+            :raise:
+                ApiCallError: failure reason
+        """
+        verify_peer = kwargs['verify_peer']
+        self._update_token(verify_peer)
+        headers = self._create_header()
+        url = self.hw_push_server.format(self.appid_push)
+        msg_body_dict = dict()
+        msg_body_dict['validate_only'] = validate_only
+        msg_body_dict['message'] = App.JSON_ENCODER.default(message)
+
+        return App._send_to_server(headers, msg_body_dict, url, verify_peer)
+
+    def subscribe_topic(self, topic, token_list):
+        """
+        :param topic: The specific topic
+        :param token_list: The token list to be added
+        :return:
+        """
+        self._update_token()
+        headers = self._create_header()
+        url = self.hw_push_topic_sub_server.format(self.appid_push)
+        msg_body_dict = {'topic': topic, 'tokenArray': token_list}
+        return App._send_to_server(headers, msg_body_dict, url)
+
+    def unsubscribe_topic(self, topic, token_list):
+        """
+
+        :param topic: The specific topic
+        :param token_list: The token list to be deleted
+        :return:
+        """
+        self._update_token()
+        headers = self._create_header()
+        url = self.hw_push_topic_unsub_server.format(self.appid_push)
+        msg_body_dict = {'topic': topic, 'tokenArray': token_list}
+        return App._send_to_server(headers, msg_body_dict, url)
+
+    def query_subscribe_list(self, token):
+        """
+        :param token:  The specific token
+        :return:
+        """
+        self._update_token()
+        headers = self._create_header()
+        url = self.hw_push_topic_query_server.format(self.appid_push)
+        msg_body_dict = {'token': token}
+        return App._send_to_server(headers, msg_body_dict, url)
+
+
+class ApiCallError(Exception):
+    """Represents an Exception encountered while invoking the HCM API.
+
+    Attributes:
+        message: A error message string.
+        detail: Original low-level exception.
+    """
+    def __init__(self, message, detail=None):
+        Exception.__init__(self, message)
+        self.detail = detail

+ 55 - 0
Service/HuaweiPushService/push_admin/_http.py

@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import requests
+
+
+def post(url, req_body, headers=None, verify_peer=False):
+    """ post http request to slb service
+        :param url: url path
+        :param req_body: http request body
+        :param headers: http headers
+        :param verify_peer:  (optional) Either a boolean, in which case it controls whether we verify
+            the server's TLS certificate, or a string, in which case it must be a path
+            to a CA bundle to use. Defaults to ``True``.
+        :return:
+            success return response
+            fali return None
+    """
+    try:
+        response = requests.post(url, data=req_body, headers=headers, timeout=10, verify=verify_peer)
+        return response
+
+    except Exception as e:
+        raise ValueError('caught exception when post {0}. {1}'.format(url, e))
+
+
+def _format_http_text(method, url, headers, body):
+    """
+    print http head and body for request or response
+
+    For examples: _format_http_text('', title, response.headers, response.text)
+    """
+    result = method + ' ' + url + '\n'
+
+    if headers is not None:
+        for key, value in headers.items():
+            result = result + key + ': ' + value + '\n'
+
+    result = result + body
+    return result
+
+

+ 613 - 0
Service/HuaweiPushService/push_admin/_message_serializer.py

@@ -0,0 +1,613 @@
+# -*-coding:utf-8-*-
+#
+# Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+from Service.HuaweiPushService.push_admin import _messages
+import six
+
+
+class MessageSerializer(json.JSONEncoder):
+    """
+    Use https://docs.python.org/3/library/json.html to do serialization
+
+    The serializer should serialize the following messages:
+    _messages.Message
+    _messages.Notification
+    _messages.ApnsConfig
+    _messages.WebPushConfig
+    _messages.WebPushNotification
+    _messages.WebPushNotificationAction
+    _messages.WebPushHMSOptions
+    _messages.AndroidConfig
+    _messages.AndroidNotification
+    _messages.AndroidClickAction
+    _messages.BadgeNotification
+    """
+    def default(self, message):
+        """
+        :param message: The push message
+        :return: formatted push messages
+        """
+        result = {
+            'data': message.data,
+            'notification': MessageSerializer.encode_notification(message.notification),
+            'android': MessageSerializer.encode_android_config(message.android),
+            'apns': MessageSerializer.encode_apns_config(message.apns),
+            'webpush': MessageSerializer.encode_webpush_config(message.web_push),
+            'token': message.token,
+            'topic': message.topic,
+            'condition': message.condition
+        }
+        result = MessageSerializer.remove_null_values(result)
+        return result
+
+    @classmethod
+    def remove_null_values(cls, dict_value):
+        return {k: v for k, v in dict_value.items() if v not in [None, [], {}]}
+
+    @classmethod
+    def encode_notification(cls, notification):
+        """
+            An example:
+           {
+              "title":"Big News",
+              "body":"This is a Big News!",
+              "image":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2_0.png"
+            }
+        :param notification:
+        :return:
+        """
+        if notification is None:
+            return None
+
+        if not isinstance(notification, _messages.Notification):
+            raise ValueError('Message.notification must be an instance of Notification class.')
+
+        result = {
+            'title': notification.title,
+            'body': notification.body,
+            'image': notification.image
+        }
+        return cls.remove_null_values(result)
+
+    @classmethod
+    def encode_android_config(cls, android_config):
+        """
+        An example:
+        {
+          "android":{
+            "collapse_key":-1,
+            "urgency":"HIGH",
+            "ttl":"1448s",
+            "bi_tag":"Trump",
+            "fast_app_target":1,
+            "notification": {}
+        }
+        :param android_config:
+        :return:
+        """
+        if android_config is None:
+            return None
+
+        if not isinstance(android_config, _messages.AndroidConfig):
+            raise ValueError('Message.android must be an instance of AndroidConfig class.')
+
+        result = {
+            'collapse_key': android_config.collapse_key,
+            'urgency': android_config.urgency,
+            'ttl': android_config.ttl,
+            'bi_tag': android_config.bi_tag,
+            'fast_app_target': android_config.fast_app_target,
+            'data': android_config.data,
+            'notification': MessageSerializer.encode_android_notification(android_config.notification)
+        }
+        return cls.remove_null_values(result)
+
+    @classmethod
+    def encode_android_notification(cls, notification):
+        """
+           "notification":{
+                "title":"Noti in Noti title",
+                "body":"Noti in Noti body",
+                "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
+                "color":"#AACCDD",
+                "default_sound":true,
+                "tag":"tagBoom",
+                "importance":"PRIORITY_HIGH",
+                "click_action":{
+                    "type":2,
+                    "url":"https://www.huawei.com"
+                },
+                "body_loc_key":"M.String.body",
+                "body_loc_args":[
+                    "Boy",
+                    "Dog"
+                ],
+                "title_loc_key":"M.String.title",
+                "title_loc_args":[
+                    "Girl",
+                    "Cat"
+                ],
+                "channel_id":"RingRing",
+                "notify_summary":"Some Summary",
+                "style":2,
+                "big_title":"Big Boom Title",
+                "big_body":"Big Boom Body",
+                "notify_id":486,
+                "group":"Espace",
+                "badge":{
+                    "add_num":99,
+                    "set_num":99,
+                    "class":"Classic"
+                },
+                "ticker":"i am a ticker",
+                "auto_cancel":false,
+                "when":"2019-11-05",
+                "use_default_vibrate":true,
+                "use_default_light":false,
+                "visibility":"PUBLIC",
+                "vibrate_config":["1.5","2","3"],
+                "light_settings":{
+                    "color":{
+                        "alpha":0,
+                        "red":0,
+                        "green":1,
+                        "blue":1
+                    },
+                    "light_on_duration":"3.5",
+                    "light_off_duration":"5S"
+                },
+                "foreground_show":true
+              }
+        :param notification:
+        :return:
+        """
+        if notification is None:
+            return None
+
+        if not isinstance(notification, _messages.AndroidNotification):
+            raise ValueError('Message.AndroidConfig.notification must be an instance of AndroidNotification class.')
+
+        result = {
+            "title": notification.title,
+            "body": notification.body,
+            "icon": notification.icon,
+            "color": notification.color,
+            "sound": notification.sound,
+            "default_sound": notification.default_sound,
+            "tag": notification.tag,
+            "importance": notification.importance,
+            "multi_lang_key": notification.multi_lang_key,
+            "click_action": MessageSerializer.encode_android_click_action(notification.click_action),
+            "body_loc_key": notification.body_loc_key,
+            "body_loc_args": notification.body_loc_args,
+            "title_loc_key": notification.title_loc_key,
+            "title_loc_args": notification.title_loc_args,
+            "channel_id": notification.channel_id,
+            "notify_summary": notification.notify_summary,
+            "image": notification.image,
+            "style": notification.style,
+            "big_title": notification.big_title,
+            "big_body": notification.big_body,
+            "notify_id": notification.notify_id,
+            "group": notification.group,
+            "badge": MessageSerializer.encode_android_badge(notification.badge),
+            "ticker": notification.ticker,
+            "auto_cancel": notification.auto_cancel,
+            "when": notification.when,
+            "use_default_vibrate": notification.use_default_vibrate,
+            "use_default_light": notification.use_default_light,
+            "visibility": notification.visibility,
+            "vibrate_config": notification.vibrate_config,
+            "light_settings": MessageSerializer.encode_android_light_settings(notification.light_settings),
+            "foreground_show": notification.foreground_show
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_android_click_action(cls, click_action):
+        """
+            "click_action":{
+                    "type":2,
+                    "url":"https://www.huawei.com"
+             }
+
+             "click_action":{
+                    "type":1,
+                    "intent":"https://www.huawei.com",
+                    "action":""
+             }
+
+        :param click_action: _messages.AndroidClickAction
+        :return:
+        """
+        if click_action is None:
+            return None
+
+        if not isinstance(click_action, _messages.AndroidClickAction):
+            raise ValueError('Message.AndroidConfig.AndroidNotification.click_action must be an instance\
+                             of AndroidClickAction class.')
+
+        result = {
+            "type": click_action.action_type,
+            "intent": click_action.intent,
+            "url": click_action.url,
+            "action": click_action.action
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_android_badge(cls, badge):
+        """
+        refer to: _messages.AndroidBadgeNotification
+
+        "badge":{
+                    "add_num":99,
+                    "set_num":99,
+                    "class":"Classic"
+                }
+
+        :param badge:
+        :return:
+        """
+        if badge is None:
+            return None
+
+        if not isinstance(badge, _messages.AndroidBadgeNotification):
+            raise ValueError('Message.AndroidConfig.AndroidNotification.badge must be an instance\
+                             of AndroidBadgeNotification class.')
+
+        result = {
+            "add_num": badge.add_num,
+            "set_num": badge.set_num,
+            "class": badge.clazz
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_android_light_settings(cls, android_light_settings):
+        """
+        refer to: _messages.AndroidLightSettings
+
+        "light_settings":{
+                "color":{
+                        "alpha":0,
+                        "red":0,
+                        "green":1,
+                        "blue":1
+                },
+                "light_on_duration":"3.5",
+                "light_off_duration":"5S"
+         }
+
+        :param android_light_settings:  _messages.AndroidLightSettings
+        :return:
+        """
+        if android_light_settings is None:
+            return None
+
+        if not isinstance(android_light_settings, _messages.AndroidLightSettings):
+            raise ValueError('Message.AndroidConfig.AndroidNotification.android_light_settings must be an instance\
+                             of AndroidLightSettings class.')
+
+        result = {
+            "color": MessageSerializer.encode_android_light_settings_color(android_light_settings.color),
+            "light_on_duration": android_light_settings.light_on_duration,
+            "light_off_duration": android_light_settings.light_off_duration
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_android_light_settings_color(cls, color):
+        """
+        "color":{
+                        "alpha":0,
+                        "red":0,
+                        "green":1,
+                        "blue":1
+        }
+
+        :param color: _messages.AndroidLightSettingsColor
+        :return:
+        """
+        if color is None:
+            return None
+
+        if not isinstance(color, _messages.AndroidLightSettingsColor):
+            raise ValueError('Message.AndroidConfig.AndroidNotification.android_light_settings.color must be an instance\
+                             of AndroidLightSettingsColor class.')
+        result = {
+            "alpha": color.alpha,
+            "red": color.red,
+            "green": color.green,
+            "blue": color.blue
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_webpush_config(cls, webpush_config):
+        """
+        "webpush":{
+            "headers":{
+                ...
+            },
+            "notification":{
+                ...
+            },
+            "hms_options":{
+                ...
+            }
+         }
+        :param webpush_config: refer to _messages.WebPushConfig
+        :return:
+        """
+        if webpush_config is None:
+            return None
+
+        if not isinstance(webpush_config, _messages.WebPushConfig):
+            raise ValueError('Message.webpush must be an instance of WebPushConfig class.')
+
+        result = {
+            "headers": MessageSerializer.encode_webpush_config_headers(webpush_config.headers),
+            "notification": MessageSerializer.encode_webpush_config_notification(webpush_config.notification),
+            "hms_options": MessageSerializer.encode_webpush_config_hms_options(webpush_config.hms_options),
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_webpush_config_headers(cls, webpush_headers):
+        """
+        "headers":{
+                "ttl":"990",
+                "urgency":"very-low",
+                "topic":"12313ceshi"
+            }
+
+        :param webpush_headers: _messages.WebPushHeader
+        :return:
+        """
+        if webpush_headers is None:
+            return None
+
+        if not isinstance(webpush_headers, _messages.WebPushHeader):
+            raise ValueError('Message.webpush.headers must be an instance of WebPushHeader class.')
+
+        result = {
+            "ttl": webpush_headers.ttl,
+            "urgency": webpush_headers.urgency,
+            "topic": webpush_headers.topic,
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_webpush_config_notification(cls, webpush_notification):
+        """
+        "notification":{
+                "title":"notication string",
+                "body":"web push body",
+                "actions":[
+                    {
+                        "action":"",
+                        "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
+                        "title":"string"
+                    }
+                ],
+                "badge":"string",
+                "dir":"auto",
+                "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
+                "image":"string",
+                "lang":"string",
+                "renotify":true,
+                "requireInteraction":true,
+                "silent":true,
+                "tag":"string",
+                "timestamp":1545201266,
+                "vibrate":[1,2,3]
+            }
+
+        :param webpush_notification: refer to _messages.WebPushNotification
+        :return:
+        """
+        if webpush_notification is None:
+            return None
+
+        if not isinstance(webpush_notification, _messages.WebPushNotification):
+            raise ValueError('Message.webpush.notification must be an instance of WebPushNotification class.')
+
+        result = {
+            "title": webpush_notification.title,
+            "body": webpush_notification.body,
+            "actions": [MessageSerializer.encode_webpush_notification_action(_)
+                        for _ in webpush_notification.actions],
+            "badge": webpush_notification.badge,
+            "dir": webpush_notification.dir,
+            "icon": webpush_notification.icon,
+            "image": webpush_notification.image,
+            "lang": webpush_notification.lang,
+            "renotify": webpush_notification.renotify,
+            "require_interaction": webpush_notification.require_interaction,
+            "silent": webpush_notification.silent,
+            "tag": webpush_notification.tag,
+            "timestamp": webpush_notification.timestamp,
+            "vibrate": webpush_notification.vibrate
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_webpush_notification_action(cls, webpush_notification_action):
+        """
+        "actions":[
+                    {
+                        "action":"",
+                        "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
+                        "title":"string"
+                    }
+                ],
+        :param webpush_notification_action: refer to _messages.WebPushNotificationAction
+        :return:
+        """
+        if webpush_notification_action is None:
+            return None
+
+        if not isinstance(webpush_notification_action, _messages.WebPushNotificationAction):
+            raise ValueError('Message.webpush.notification.action must be an instance of \
+                            WebPushNotificationAction class.')
+
+        result = {
+            "action": webpush_notification_action.action,
+            "icon": webpush_notification_action.icon,
+            "title": webpush_notification_action.title
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_webpush_config_hms_options(cls, webpush_hms_options):
+        """
+        "hms_options":{
+                "link":"https://www.huawei.com/"
+         }
+
+        :param webpush_hms_options: refer to _messages.WebPushHMSOptions
+        :return:
+        """
+        if webpush_hms_options is None:
+            return None
+
+        if not isinstance(webpush_hms_options, _messages.WebPushHMSOptions):
+            raise ValueError('Message.webpush.hmsoptions must be an instance of \
+                            WebPushHMSOptions class.')
+
+        result = {
+            "link": webpush_hms_options.link
+        }
+        result = cls.remove_null_values(result)
+        return result
+
+    @classmethod
+    def encode_apns_config(cls, apns_config):
+        """
+        Encode APNs config into JSON
+        :param apns_config:
+        :return:
+        """
+        if apns_config is None:
+            return None
+        if not isinstance(apns_config, _messages.APNsConfig):
+            raise ValueError('Message.apns_config must be an instance of _messages.APNsConfig class.')
+
+        result = {
+            'headers': apns_config.headers,
+            'payload': cls.encode_apns_payload(apns_config.payload),
+            'hms_options': cls.encode_apns_hms_options(apns_config.apns_hms_options)
+        }
+        return cls.remove_null_values(result)
+
+    @classmethod
+    def encode_apns_payload(cls, apns_payload):
+        """Encodes an ``APNSPayload`` instance into JSON."""
+        if apns_payload is None:
+            return None
+        if not isinstance(apns_payload, _messages.APNsPayload):
+            raise ValueError('APNSConfig.payload must be an instance of _messages.APNsPayload class.')
+        result = {
+            'aps': cls.encode_apns_payload_aps(apns_payload.aps)
+        }
+        for key, value in apns_payload.custom_data.items():
+            result[key] = value
+        return cls.remove_null_values(result)
+
+    @classmethod
+    def encode_apns_payload_aps(cls, apns_payload_aps):
+        """Encodes an ``Aps`` instance into JSON."""
+        if not isinstance(apns_payload_aps, _messages.APNsAps):
+            raise ValueError('APNSPayload.aps must be an instance of _messages.APNsAps class.')
+
+        result = {
+            'alert': cls.encode_apns_payload_alert(apns_payload_aps.alert),
+            'badge': apns_payload_aps.badge,
+            'sound': apns_payload_aps.sound,
+            'category': apns_payload_aps.category,
+            'thread-id': apns_payload_aps.thread_id
+        }
+
+        if apns_payload_aps.content_available is True:
+            result['content-available'] = 1
+        if apns_payload_aps.mutable_content is True:
+            result['mutable-content'] = 1
+        if apns_payload_aps.custom_data is not None:
+            if not isinstance(apns_payload_aps.custom_data, dict):
+                raise ValueError('Aps.custom_data must be a dict.')
+            for key, val in apns_payload_aps.custom_data.items():
+                if key in result:
+                    raise ValueError('Multiple specifications for {0} in Aps.'.format(key))
+                result[key] = val
+        return cls.remove_null_values(result)
+
+    @classmethod
+    def encode_apns_payload_alert(cls, apns_payload_alert):
+        """Encodes an ``ApsAlert`` instance into JSON."""
+        if apns_payload_alert is None:
+            return None
+        if isinstance(apns_payload_alert, six.string_types):
+            return apns_payload_alert
+        if not isinstance(apns_payload_alert, _messages.APNsAlert):
+            raise ValueError('Aps.alert must be a string or an instance of _messages.APNsAlert class.')
+        result = {
+            'title': apns_payload_alert.title,
+            'body': apns_payload_alert.body,
+            'title-loc-key': apns_payload_alert.title_loc_key,
+            'title-loc-args': apns_payload_alert.title_loc_args,
+            'loc-key': apns_payload_alert.loc_key,
+            'loc-args': apns_payload_alert.loc_args,
+            'action-loc-key': apns_payload_alert.action_loc_key,
+            'launch-image': apns_payload_alert.launch_image
+        }
+        if result.get('loc-args') and not result.get('loc-key'):
+            raise ValueError(
+                'ApsAlert.loc_key is required when specifying loc_args.')
+        if result.get('title-loc-args') and not result.get('title-loc-key'):
+            raise ValueError(
+                'ApsAlert.title_loc_key is required when specifying title_loc_args.')
+        if apns_payload_alert.custom_data is not None:
+            if not isinstance(apns_payload_alert.custom_data, dict):
+                raise ValueError('ApsAlert.custom_data must be a dict.')
+            for key, val in apns_payload_alert.custom_data.items():
+                result[key] = val
+        return cls.remove_null_values(result)
+
+    @classmethod
+    def encode_apns_hms_options(cls, apns_hms_options):
+        """
+        :param apns_hms_options:
+        """
+        if apns_hms_options is None:
+            return None
+        if not isinstance(apns_hms_options, _messages.APNsHMSOptions):
+            raise ValueError('Aps.alert must be a string or an instance of _messages.APNsHMSOptions class.')
+
+        result = {
+            'target_user_type': apns_hms_options.target_user_type,
+        }
+        return cls.remove_null_values(result)

+ 915 - 0
Service/HuaweiPushService/push_admin/_messages.py

@@ -0,0 +1,915 @@
+# -*-coding:utf-8-*-
+#
+# Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numbers
+import re
+import six
+
+
+class Message(object):
+    """A message that can be sent Huawei Cloud Messaging.
+
+    Args:
+        data: A string value.
+        notification: An instance of ``messaging.Notification`` (optional).
+        android: An instance of ``messaging.Android`` (optional).
+        apns: APSN related message definition
+        web_push: Web Push related message definition
+        token: token list, must be tuple (optional).
+        topic: message topic, must be string (optional).
+        condition: message condition, must be string (optional).
+    """
+    def __init__(self, data=None, notification=None, android=None, apns=None, web_push=None, token=None,
+                 topic=None, condition=None):
+        MessageValidator.check_message(data, notification, android, apns, web_push, token, topic, condition)
+        self.data = data
+        self.notification = notification
+        self.android = android
+        self.apns = apns
+        self.web_push = web_push
+        self.token = token
+        self.topic = topic
+        self.condition = condition
+
+
+class Notification(object):
+    """A notification that can be included in a message.
+
+    Args:
+        title: Title of the notification (optional).
+        body: Body of the notification (optional).
+    """
+    def __init__(self, title=None, body=None, image=None):
+        MessageValidator.check_notification(title, body, image)
+        self.title = title
+        self.body = body
+        self.image = image
+
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+class APNsConfig(object):
+    """
+    Please refer to the Apple APNS API reference:
+    https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/\
+    CommunicatingwithAPNs.html
+    """
+    def __init__(self, headers=None, payload=None, apns_hms_options=None):
+        MessageValidator.check_apns_config(headers=headers, payload=payload, apns_hms_options=apns_hms_options)
+        self.headers = headers
+        self.payload = payload
+        self.apns_hms_options = apns_hms_options
+
+
+class APNsHeader(object):
+    """
+    authorization
+    apns-id
+    apns-expiration
+    apns-priority
+    apns-topic
+    apns-collapse-id
+    """
+    HEAD_AUTHORIZATION = "authorization"
+    HEAD_APNs_ID = "apns-id"
+    HEAD_APNs_EXPIRATION = "apns-expiration"
+    HEAD_APNs_PRIORITY = "apns-priority"
+    HEAD_APNs_TOPIC = "pns-topic"
+    HEAD_APNs_COLLAPSE_ID = "apns-collapse-id"
+
+
+class APNsPayload(object):
+    """
+     APNs payload definition
+    """
+    def __init__(self, aps, **kwargs):
+        MessageValidator.check_apns_payload(aps=aps)
+        self.aps = aps
+        self.custom_data = kwargs
+
+
+class APNsAps(object):
+    """
+    APNs aps definition: https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual\
+                        /RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1
+
+    one sample is as follows:
+
+    {
+        "aps" : {
+            "alert" : {
+                "title" : "Game Request",
+                "body" : "Bob wants to play poker",
+                "action-loc-key" : "PLAY"
+                "loc-key" : "GAME_PLAY_REQUEST_FORMAT",
+                "loc-args" : [ "Jenna", "Frank"],
+                "content-available" : 1
+            },
+            "badge" : 5,
+            "sound" : "bingbong.aiff",
+        },
+        "acme1" : "bar",
+        "acme2" : [ "bang",  "whiz" ]
+    }
+    """
+    def __init__(self, alert=None, badge=None, sound=None, content_available=None, category=None,
+                 thread_id=None, mutable_content=None, custom_data=None):
+        MessageValidator.check_apns_payload_aps(alert=alert, badge=badge, sound=sound,
+                                                content_available=content_available, category=category,
+                                                thread_id=thread_id, mutable_content=mutable_content,
+                                                custom_data=custom_data)
+        self.alert = alert
+        self.badge = badge
+        self.sound = sound
+        self.content_available = content_available
+        self.category = category
+        self.thread_id = thread_id
+        self.mutable_content = mutable_content
+        self.custom_data = custom_data
+
+
+class APNsAlert(object):
+    """An alert that can be included in ``messaging.Aps``.
+
+    Args:
+
+    """
+
+    def __init__(self, title=None, body=None, loc_key=None, loc_args=None,
+                 title_loc_key=None, title_loc_args=None, action_loc_key=None, launch_image=None,
+                 custom_data=None):
+        MessageValidator.check_apns_payload_aps_alert(title=title, body=body, loc_key=loc_key, loc_args=loc_args,
+                                                      title_loc_key=title_loc_key, title_loc_args=title_loc_args,
+                                                      action_loc_key=action_loc_key, launch_image=launch_image,
+                                                      custom_data=custom_data)
+        self.title = title
+        self.body = body
+        self.loc_key = loc_key
+        self.loc_args = loc_args
+        self.title_loc_key = title_loc_key
+        self.title_loc_args = title_loc_args
+        self.action_loc_key = action_loc_key
+        self.launch_image = launch_image
+        self.custom_data = custom_data
+
+
+class APNsHMSOptions(object):
+    """Options for features provided by the FCM SDK for iOS.
+
+    Args:
+        target_user_type: Developer or Commercial enviroment
+    """
+    def __init__(self, target_user_type=None):
+        MessageValidator.check_apns_hms_options(target_user_type=target_user_type)
+        self.target_user_type = target_user_type
+
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+class WebPushConfig(object):
+    """
+        Web push-specific options that can be included in a message.
+        For Web Push Specification Reference: https://tools.ietf.org/html/rfc8030#section-5
+        For mozilla implementation: https://developer.mozilla.org/en-US/docs/Web/API/notification
+    """
+    TTL_HEADER = "ttl"
+    URGENCY_HEADER = "urgency"
+    TOPIC_HEADER = "topic"
+
+    def __init__(self, headers=None, data=None, notification=None, hms_options=None):
+        """
+
+        :param headers: A dictionary of headers (optional). Refer `Web push Specification`_ for supported headers.
+        :param notification:  A ``messaging.WebPushNotification`` to be included in the message (optional).
+        :param hms_options:  A ``WebPushHMSOptions`` instance to be included in the message(optional).
+        """
+        MessageValidator.check_webpush_config(headers, data, notification, hms_options)
+        """ Refer to https://tools.ietf.org/html/rfc7240 """
+        self.headers = headers
+        """ message deliver to the end application directly """
+        self.data = data
+        """ Refer to  WebPushNotification """
+        self.notification = notification
+        """ Refer to WebPushHMSOptions"""
+        self.hms_options = hms_options
+
+
+class WebPushHeader(object):
+    """
+     Web Push Header, refer to: https://tools.ietf.org/html/rfc7240
+    """
+    def __init__(self, ttl=None, urgency=None, topic=None):
+        MessageValidator.check_webpush_header(ttl, urgency, topic)
+        self.ttl = ttl
+        self.urgency = urgency
+        self.topic = topic
+
+
+class WebPushNotification(object):
+    """
+     Web Push Notification
+    """
+    def __init__(self, title=None, body=None, icon=None, actions=None, badge=None, data=None, dir=None,
+                 image=None, lang=None, renotify=None, require_interaction=None, silent=None, tag=None,
+                 timestamp=None, vibrate=None):
+        MessageValidator.check_webpush_notification(title=title, body=body, icon=icon, actions=actions, badge=badge,
+                                                    data=data, dir=dir, image=image, lang=lang,
+                                                    renotify=renotify, require_interaction=require_interaction,
+                                                    silent=silent, tag=tag, timestamp=timestamp, vibrate=vibrate)
+        self.title = title
+        self.body = body
+        """ Refer to WebPushNotificationAction """
+        self.actions = actions
+        self.badge = badge
+        self.data = data
+        self.dir = dir
+        self.icon = icon
+        self.image = image
+        self.lang = lang
+        self.renotify = renotify
+        self.require_interaction = require_interaction
+        self.silent = silent
+        self.tag = tag
+        self.timestamp = timestamp
+        self.vibrate = vibrate
+
+
+class WebPushNotificationAction(object):
+    """
+    The action for web push notification
+    """
+    def __init__(self, action=None, title=None, icon=None):
+        """
+
+        :param action:
+        :param title:
+        :param icon:
+        """
+        MessageValidator.check_webpush_notification_action(action=action, title=title, icon=icon)
+        self.action = action
+        self.icon = icon
+        self.title = title
+
+
+class WebPushHMSOptions(object):
+    """
+    optional link option
+    """
+    def __init__(self, link=None):
+        MessageValidator.check_webpush_hms_options(link)
+        self.link = link
+
+
+# ----------------------------------------------------------------------------------------------------------------------
+
+
+class AndroidConfig(object):
+
+    HIGH_PRIORITY = "HIGH"
+    NORMAL_PRIORITY = "NORMAL"
+
+    """
+    Android-specific options that can be included in a message.
+    """
+    def __init__(self, collapse_key=None, urgency='NORMAL', ttl=None, bi_tag=None
+                 , fast_app_target=None, notification=None, data=None, category=None):
+        MessageValidator.check_android_config(collapse_key, urgency, ttl, bi_tag, fast_app_target
+                                              , notification, data)
+        self.collapse_key = collapse_key
+        self.urgency = urgency
+        self.ttl = ttl
+        self.bi_tag = bi_tag
+        self.fast_app_target = fast_app_target
+        self.notification = notification
+        self.data = data
+        self.category = category
+
+
+class AndroidNotification(object):
+
+    PRIORITY_LOW = "LOW"
+    PRIORITY_DEFAULT = "NORMAL"
+    PRIORITY_HIGH = "HIGH"
+    VISIBILITY_UNSPECIFIED = "VISIBILITY_UNSPECIFIED"
+    PRIVATE = "PRIVATE"
+    PUBLIC = "PUBLIC"
+    SECRET = "SECRET"
+
+    """
+    Android-specific notification parameters.
+    """
+
+    def __init__(self, title=None, body=None, icon=None, color=None, sound=None, default_sound=None, tag=None,
+                 click_action=None, body_loc_key=None, body_loc_args=None, title_loc_key=None,
+                 title_loc_args=None, multi_lang_key=None, channel_id=None, notify_summary=None, image=None,
+                 style=None, big_title=None, big_body=None, auto_clear=None, notify_id=None, group=None, badge=None,
+                 ticker=None, auto_cancel=None, when=None, importance=None, use_default_vibrate=True,
+                 use_default_light=True, vibrate_config=None, visibility=None, light_settings=None, foreground_show=False):
+
+        MessageValidator.check_android(title=title, body=body, icon=icon, color=color, sound=sound,
+                                       default_sound=default_sound, tag=tag, click_action=click_action,
+                                       body_loc_key=body_loc_key, body_loc_args=body_loc_args,
+                                       title_loc_key=title_loc_key, title_loc_args=title_loc_args,
+                                       multi_lang_key=multi_lang_key, channel_id=channel_id,
+                                       notify_summary=notify_summary,
+                                       image=image, style=style, big_title=big_title, big_body=big_body,
+                                       auto_clear=auto_clear, notify_id=notify_id,
+                                       group=group, badge=badge, ticker=ticker, auto_cancel=auto_cancel, when=when,
+                                       importance=importance,
+                                       use_default_vibrate=use_default_vibrate,
+                                       use_default_light=use_default_light, vibrate_config=vibrate_config,
+                                       visibility=visibility, light_settings=light_settings,
+                                       foreground_show=foreground_show)
+        self.title = title
+        self.body = body
+        self.icon = icon
+        self.color = color
+        self.sound = sound
+        self.default_sound = default_sound
+        self.tag = tag
+        self.click_action = click_action
+        self.body_loc_key = body_loc_key
+        self.body_loc_args = body_loc_args
+        self.title_loc_key = title_loc_key
+        self.title_loc_args = title_loc_args
+        self.multi_lang_key = multi_lang_key
+        self.channel_id = channel_id
+        self.notify_summary = notify_summary
+        self.image = image
+        self.style = style
+        self.big_title = big_title
+        self.big_body = big_body
+        self.auto_clear = auto_clear
+        self.notify_id = notify_id
+        self.group = group
+        self.badge = badge
+        self.ticker = ticker
+        self.auto_cancel = auto_cancel
+        self.when = when
+        self.importance = importance
+        self.use_default_vibrate = use_default_vibrate
+        self.use_default_light = use_default_light
+        self.vibrate_config = vibrate_config
+        self.visibility = visibility
+        self.light_settings = light_settings
+        self.foreground_show = foreground_show
+
+
+class AndroidClickAction(object):
+    """A ClickAction that can be included in a message.android.notification.
+
+    Args:
+        action_type: type of the android.notification (optional).
+        intent: intent of the android.notification (optional).
+        url: url of the android.notification (optional).
+        action: action definition for push message
+                1: to specific activity of application
+                2: specific URL
+                3: to specific application
+    """
+    def __init__(self, action_type=None, intent=None, action=None, url=None):
+        MessageValidator.check_click_action(action_type=action_type, intent=intent, action=action, url=url)
+        self.action_type = action_type
+        self.intent = intent
+        self.action = action
+        self.url = url
+
+class AndroidBadgeNotification(object):
+    """A BadgeNotification that can be included in a message.android.notification.
+
+    Args:
+        add_num: message number of badge notification in the android.notification (optional).
+        set_num: set the specific number of badge notification (optional).
+        clazz: message class of badge notification in the android.notification (optional).
+    """
+    def __init__(self, add_num=None, set_num=None, clazz=None):
+        MessageValidator.check_badge_notification(add_num=add_num, set_num=set_num, clazz=clazz)
+        self.add_num = add_num
+        self.set_num = set_num
+        self.clazz = clazz
+
+
+class AndroidLightSettings(object):
+    """
+        light_settings":{
+            "color":{
+                "alpha":0,
+                "red":0,
+                "green":1,
+                "blue":1
+            },
+            "light_on_duration":"3.5",
+            "light_off_duration":"5S"
+        }
+    """
+    def __init__(self, color=None, light_on_duration=None, light_off_duration=None):
+        MessageValidator.check_light_settings(color=color, light_on_duration=light_on_duration, light_off_duration=light_off_duration)
+        self.color = color
+        self.light_on_duration = light_on_duration
+        self.light_off_duration = light_off_duration
+
+
+class AndroidLightSettingsColor(object):
+    """
+        "color":{
+                "alpha":0,
+                "red":0,
+                "green":1,
+                "blue":1
+            }
+    """
+    def __init__(self, alpha=None, red=None, green=None, blue=None):
+        MessageValidator.check_light_settings_color(alpha=alpha, red=red, green=green, blue=blue)
+        self.alpha = alpha
+        self.red = red
+        self.green = green
+        self.blue = blue
+
+# --------------------------------------------------------------------------------------------------------------------
+
+
+class MessageValidator(object):
+    """
+        message validation utilities.
+        Methods provided in this class raise ValueErrors if any validations fail.
+    """
+    @classmethod
+    def check_https_url(cls, hint, value):
+        cls.check_string(hint, value)
+        if value is not None and not re.match(r"^https:/{2}\w.+$", value):
+            raise ValueError('{0} must be a valid https url.'.format(hint))
+
+    @classmethod
+    def check_string(cls, hint, value, non_empty=False):
+        """Checks if the given value is a string."""
+        if value is None:
+            return None
+        if not isinstance(value, six.string_types):
+            if non_empty:
+                raise ValueError('{0} must be a non-empty string.'.format(hint))
+            else:
+                raise ValueError('{0} must be a string.'.format(hint))
+        if non_empty and not value:
+            raise ValueError('{0} must be a non-empty string.'.format(hint))
+        return value
+
+    @classmethod
+    def assert_string_values(cls, hint, value, *args):
+        """
+        Check the class value should be an instance of string, and related values should be within *args
+        :param hint: prompt message
+        :param value: the real value
+        :param args: the specific value list
+        :return:
+        """
+        if value is None:
+            return None
+        if not isinstance(value, six.string_types):
+                raise ValueError('{0} must be a string.'.format(hint))
+        for v in args:
+            if value.__eq__(v):
+                return value
+
+        raise ValueError('{} must be a value within{}.'.format(hint, args))
+
+    @classmethod
+    def check_string_list(cls, label, value):
+        """Checks if the given value is a list comprised only of strings."""
+        if value is None or value == []:
+            return None
+        if not isinstance(value, list):
+            raise ValueError('{0} must be a list of strings.'.format(label))
+        non_str = [k for k in value if not isinstance(k, six.string_types)]
+        if non_str:
+            raise ValueError('{0} must not contain non-string values.'.format(label))
+        return value
+
+    @classmethod
+    def check_boolean(cls, hint, value):
+        """Checks if the given value is a string."""
+        if value is None:
+            return None
+        if not isinstance(value, bool):
+            raise ValueError('{0} must be a boolean.'.format(hint))
+        return value
+
+    @classmethod
+    def count_boolean(cls, *args):
+        count = 0
+        for v in args:
+            if v:
+                count += 1
+        return count
+
+    @classmethod
+    def check_not_all_none(cls, hint, *args):
+        total_size = len(args)
+        count = 0
+        for data in args:
+            if data is None:
+                count += 1
+        if total_size == count:
+            raise ValueError(hint)
+
+    @classmethod
+    def check_type(cls, class_obj, class_type, hint):
+        if (class_obj is not None) and (not isinstance(class_obj, class_type)):
+            raise ValueError(hint)
+
+    @classmethod
+    def check_type_list(cls, label, value, cls_type):
+        """Checks if the given value is a list comprised only of numbers."""
+        if value is None or value == []:
+            return None
+        if not isinstance(value, list):
+            raise ValueError('{0} must be a list of {1}.'.format(label, cls_type))
+        non_number = [k for k in value if not isinstance(k, cls_type)]
+        if non_number:
+            raise ValueError('{0} must not contain non-{1} values.'.format(label, cls_type))
+        return value
+
+    @classmethod
+    def check_number(cls, label, value):
+        if value is None:
+            return None
+        if not isinstance(value, numbers.Number):
+            raise ValueError('{0} must be a number.'.format(label))
+        return value
+
+    @classmethod
+    def check_number_span(cls, label, value, min, max):
+        if value is None:
+            return None
+        if not isinstance(value, numbers.Number):
+            raise ValueError('{0} must be a number.'.format(label))
+        if value < min or value > max:
+            raise ValueError('{0} must be within {1} to {2}.'.format(label, min, max))
+        return value
+
+    @classmethod
+    def assert_integer_values(cls, hint, value, *args):
+        """
+        Check the class value should be an instance of string, and related values should be within *args
+        :param hint: prompt message
+        :param value: the real value
+        :param args: the specific value list
+        :return:
+        """
+        if value is None:
+            return None
+        if not isinstance(value, six.integer_types):
+            raise ValueError('{0} must be a integer.'.format(hint))
+        for v in args:
+            if value == v:
+                return value
+
+        raise ValueError('{} must be a value within{}.'.format(hint, args))
+
+    @classmethod
+    def check_number_list(cls, label, value):
+        if value is None or value == []:
+            return None
+        if not isinstance(value, list):
+            raise ValueError('{0} must be a list of numbers.'.format(label))
+        non_number = [k for k in value if not isinstance(k, numbers.Number)]
+        if non_number:
+            raise ValueError('{0} must not contain non-number values.'.format(label))
+        return value
+
+    @classmethod
+    def check_string_dict(cls, label, value):
+        if value is None or value == {}:
+            return None
+        if not isinstance(value, dict):
+            raise ValueError('{0} must be a dictionary.'.format(label))
+        non_str = [k for k in value if not isinstance(k, six.string_types)]
+        if non_str:
+            raise ValueError('{0} must not contain non-string keys.'.format(label))
+        return value
+
+    # ------------------------------------------------------------------------------------------------------------------
+
+    @classmethod
+    def check_message(cls, data, notification, android, apns, web_push, token, topic, condition):
+        """
+        Check whether the message parameter is valid or not
+
+        :param data:
+        :param notification:
+        :param android:
+        :param apns:
+        :param web_push:
+        :param token:
+        :param topic:
+        :param condition:
+        :return:
+        """
+        # data must be string
+        cls.check_string(hint="Message.data", value=data)
+
+        # notification
+        if (notification is not None) and (not isinstance(notification, Notification)):
+            raise ValueError('notification must be an instance of Notification class')
+
+        # android / APNs / Web Push
+        # if notification message(data is None), one of android / APNs / Web Push must be present
+        if data is None:
+            cls.check_not_all_none('Message.data is None, one of Message.android/Message.apns/Message.webpush \
+            must be present', android, apns, web_push)
+
+        cls.check_type(android, AndroidConfig, 'android must be an instance of AndroidConfig class')
+        cls.check_type(apns, APNsConfig, 'apns must be an instance of APNsConfig class')
+        cls.check_type(web_push, WebPushConfig, 'web_push must be an instance of WebPushConfig class')
+
+        """token, topic, condition"""
+        # [token, topic, condition] only one not None
+        target_count = cls.count_boolean(token is not None, topic is not None, condition is not None)
+        if target_count != 1:
+            raise ValueError('Exactly one of token, topic or condition must be specified.')
+
+        # token must be tuple or list
+        if token is not None:
+            if not isinstance(token, tuple) and not isinstance(token, list):
+                raise ValueError('token must be a tuple or a list')
+            if len(token) > 1000:
+                raise ValueError('token must not contain more than 1000 tokens')
+
+        cls.check_string(hint="Message.topic", value=topic)
+        cls.check_string(hint="Message.condition", value=condition)
+
+    @classmethod
+    def check_notification(cls, title, body, image):
+        cls.check_string(hint="Notification.title", value=title)
+        cls.check_string(hint="Notification.body", value=body)
+        cls.check_https_url(hint="Notification.image", value=image)
+
+    @classmethod
+    def check_android_config(cls, collapse_key, urgency, ttl, bi_tag, fast_app_target, notification, data):
+        # collapse_key
+        cls.check_number('AndroidConfig.collapse_key', collapse_key)
+        # urgency
+        cls.assert_string_values("AndroidConfig.urgency", urgency, AndroidConfig.HIGH_PRIORITY,
+                                 AndroidConfig.NORMAL_PRIORITY)
+        # ttl
+        cls.check_string(hint="AndroidConfig.ttl", value=ttl)
+        # bi_tag
+        cls.check_string(hint="AndroidConfig.bi_tag", value=bi_tag)
+        # fast_app_target
+        cls.check_number_span("AndroidConfig.fast_app_target", fast_app_target, 1, 2)
+        # notification
+        cls.check_type(notification, AndroidNotification,
+                       hint='notification must be an instance of AndroidNotification')
+        # data
+        cls.check_string(hint="AndroidConfig.data", value=data)
+
+    @classmethod
+    def check_android(cls, title, body, icon, color, sound, default_sound, tag, click_action, body_loc_key, body_loc_args,
+                      title_loc_key, title_loc_args, multi_lang_key, channel_id, notify_summary, image,
+                      style, big_title, big_body, auto_clear, notify_id, group, badge,
+                      ticker, auto_cancel, when, importance,
+                      use_default_vibrate, use_default_light, vibrate_config, visibility, light_settings,
+                      foreground_show):
+        # title
+        cls.check_string(hint="AndroidNotification.title", value=title)
+        # body
+        cls.check_string(hint="AndroidNotification.body", value=body)
+        # icon
+        cls.check_string(hint="AndroidNotification.icon", value=icon)
+        # color
+        cls.check_string(hint="AndroidNotification.color", value=color)
+        # sound
+        cls.check_string(hint="AndroidNotification.sound", value=sound)
+        # default_sound
+        cls.check_boolean(hint="AndroidNotification.default_sound", value=default_sound)
+        # tag
+        cls.check_string(hint="AndroidNotification.tag", value=tag)
+        # click_action
+        cls.check_type(click_action, AndroidClickAction,
+                       hint='click_action must be an instance of AndroidClickAction')
+        # body_loc_key
+        cls.check_string(hint="AndroidNotification.body_loc_key", value=body_loc_key)
+        # body_loc_args
+        if (body_loc_args is not None) and (not isinstance(body_loc_args, tuple)) and (not isinstance(body_loc_args, list)):
+            raise ValueError('AndroidNotification.body_loc_args must be an instance of tuple or list')
+        # title_loc_key
+        cls.check_string(hint="AndroidNotification.title_loc_key", value=title_loc_key)
+        # title_loc_args
+        if (title_loc_args is not None) and (not isinstance(title_loc_args, tuple) and not isinstance(title_loc_args, list)):
+            raise ValueError('AndroidNotification.title_loc_args must be an instance of tuple or list')
+        # multi_lang_key
+        if multi_lang_key is not None:
+            if not isinstance(multi_lang_key, dict):
+                raise ValueError('AndroidNotification.multi_lang_key must be a dict.')
+        # channel_id
+        cls.check_string(hint="AndroidNotification.channel_id", value=channel_id)
+        # notify_summary
+        cls.check_string(hint="AndroidNotification.notify_summary", value=notify_summary)
+        #
+        # image
+        cls.check_https_url(hint="AndroidNotification.image", value=image)
+        # style
+        if style is not None:
+            if style not in [0, 1, 2]:
+                raise ValueError('AndroidNotification.style must in [0, 1, 2]')
+            # big_title, big_body
+            if style == 1:
+                if (big_title is None) or (not isinstance(big_title, str)):
+                    raise ValueError('AndroidNotification.big_title must be valid string when style is 1')
+                if (big_body is None) and (not isinstance(big_body, str)):
+                    raise ValueError('AndroidNotification.big_body must be valid string when style is 1')
+        # auto_clear
+        cls.check_number(label='AndroidNotification.auto_clear ', value=auto_clear)
+        # notify_id
+        cls.check_number(label='AndroidNotification.notify_id ', value=notify_id)
+        # group
+        cls.check_string(hint="AndroidNotification.group", value=group)
+        # badge
+        cls.check_type(badge, AndroidBadgeNotification, "badge should be an instance of AndroidBadgeNotification")
+        # ticker
+        cls.check_string(hint="AndroidNotification.ticker", value=ticker)
+        # auto_cancel
+        cls.check_boolean(hint="AndroidNotification.auto_cancel", value=auto_cancel)
+        # when
+        cls.check_string(hint="AndroidNotification.when", value=when)
+        # importance
+        cls.assert_string_values("AndroidNotification.importance", importance,
+                                 AndroidNotification.PRIORITY_DEFAULT,
+                                 AndroidNotification.PRIORITY_HIGH, AndroidNotification.PRIORITY_LOW)
+        # use_default_vibrate
+        cls.check_boolean(hint="AndroidNotification.use_default_vibrate", value=use_default_vibrate)
+        # use_default_light
+        cls.check_boolean(hint="AndroidNotification.use_default_light", value=use_default_light)
+        # vibrate_config
+        cls.check_string_list(label="AndroidNotification.vibrate_config", value=vibrate_config)
+        # visibility
+        cls.assert_string_values("AndroidNotification.visibility", visibility,
+                                 AndroidNotification.PRIVATE,
+                                 AndroidNotification.PUBLIC, AndroidNotification.SECRET,
+                                 AndroidNotification.VISIBILITY_UNSPECIFIED)
+        # light_settings
+        cls.check_type(light_settings, AndroidLightSettings, "light_settings should be an instance of AndroidLightSettings")
+        # foreground_show
+        cls.check_boolean(hint="AndroidNotification.foreground_show", value=foreground_show)
+
+    @classmethod
+    def check_badge_notification(cls, add_num, set_num, clazz):
+        # add_num must be int
+        cls.check_number_span(label="AndroidBadgeNotification.add_num", value=add_num, min=0, max=100)
+        # set_num must be int
+        cls.check_number_span(label="AndroidBadgeNotification.set_num", value=set_num, min=0, max=100)
+        # clazz
+        cls.check_string(hint="AndroidBadgeNotification.clazz", value=clazz)
+
+    @classmethod
+    def check_click_action(cls, action_type, intent, action, url):
+        # type must be in [1, 4]
+        if (action_type is None) or (action_type not in [1, 2, 3, 4]):
+            raise ValueError('ClickAction.type must be in [1, 2, 3, 4]')
+
+        # intent, if type is 1, intent or action must be present or both
+        if action_type == 1:
+            count = cls.count_boolean(isinstance(intent, str), isinstance(action, str))
+            if count <= 0:
+                raise ValueError('ClickAction.intent or ClickAction.action must be present or both when click_type is 1')
+
+        # url, if type is 2, url must
+        if action_type == 2:
+            if not isinstance(url, str):
+                raise ValueError('ClickAction.url must when ClickAction.type is 2')
+            if not url.upper().startswith('HTTPS'):
+                raise ValueError('ClickAction.url must be https prefix when ClickAction.type is 2')
+
+    @classmethod
+    def check_light_settings(cls, color, light_on_duration, light_off_duration):
+        cls.check_type(color, AndroidLightSettingsColor, "color must be an instance of AndroidLightSettingsColor")
+        cls.check_string(hint="AndroidLightSettings.light_on_duration", value=light_on_duration)
+        cls.check_string(hint="AndroidLightSettings.light_off_duration", value=light_off_duration)
+
+    @classmethod
+    def check_light_settings_color(cls, alpha, red, green, blue):
+        cls.check_number("AndroidLightSettingsColor.alpha", alpha)
+        cls.check_number("AndroidLightSettingsColor.red", red)
+        cls.check_number("AndroidLightSettingsColor.green", green)
+        cls.check_number("AndroidLightSettingsColor.blue", blue)
+
+    @classmethod
+    def check_webpush_config(cls, headers, data, notification, hms_options):
+        # headers
+        cls.check_type(headers, WebPushHeader, "headers must be an instance of WebPushHeader")
+        cls.check_string(hint="WebPushConfig.headers", value=data)
+        cls.check_type(notification, WebPushNotification, "notification must be an instance of WebPushNotification")
+        cls.check_type(hms_options, WebPushHMSOptions, "hms_options must be an instance of WebPushHMSOptions")
+
+    @classmethod
+    def check_webpush_header(cls, ttl=None, urgency=None, topic=None):
+        cls.check_string(hint="WebPushHeader.ttl", value=ttl)
+        cls.check_string(hint="WebPushHeader.urgency", value=urgency)
+        cls.check_string(hint="WebPushHeader.topic", value=topic)
+
+    @classmethod
+    def check_webpush_notification(cls, title=None, body=None, icon=None, actions=None, badge=None,
+                                   data=None, dir=None, image=None, lang=None, renotify=None,
+                                   require_interaction=None, silent=None, tag=None, timestamp=None, vibrate=None):
+        cls.check_string(hint="WebPushNotification.title", value=title)
+        cls.check_string(hint="WebPushNotification.body", value=body)
+        cls.check_string(hint="WebPushNotification.icon", value=icon)
+        cls.check_string(hint="WebPushNotification.data", value=data)
+        cls.check_type_list("WebPushNotificationAction.actions", actions, WebPushNotificationAction)
+        cls.check_string(hint="WebPushNotification.image", value=image)
+        cls.check_string(hint="WebPushNotification.lang", value=lang)
+        cls.check_string(hint="WebPushNotification.tag", value=tag)
+        cls.check_string(hint="WebPushNotification.badge", value=badge)
+        cls.assert_string_values("WebPushNotification.dir", dir, "auto", "ltr", "rtl")
+        cls.check_number_list("WebPushNotification.vibrate", vibrate)
+        cls.check_boolean("WebPushNotification.renotify", renotify)
+        cls.check_boolean("WebPushNotification.require_interaction", require_interaction)
+        cls.check_boolean("WebPushNotification.silent", silent)
+        cls.check_number("WebPushNotification.timestamp", timestamp)
+
+    @classmethod
+    def check_webpush_notification_action(cls, action=None, title=None, icon=None):
+        cls.check_string(hint="WebPushNotificationAction.action", value=action)
+        cls.check_string(hint="WebPushNotificationAction.title", value=title)
+        cls.check_string(hint="WebPushNotificationAction.icon", value=icon)
+
+    @classmethod
+    def check_webpush_hms_options(cls, link):
+        cls.check_string(hint="WebPushHMSOptions.link", value=link)
+
+    @classmethod
+    def check_apns_config(cls, headers=None, payload=None, apns_hms_options=None):
+        cls.check_string_dict("APNsConfig.headers", headers)
+        cls.check_type(payload, APNsPayload, "payload must be an instance of APNsPayload")
+        cls.check_type(apns_hms_options, APNsHMSOptions, "apns_hms_options must be an instance of APNsHMSOptions")
+
+    @classmethod
+    def check_apns_payload(cls, aps=None):
+        cls.check_type(aps, APNsAps, "aps must be an instance of APNsAps")
+        pass
+
+    @classmethod
+    def check_apns_payload_aps(cls, alert, badge, sound, content_available, category, thread_id, mutable_content,
+                               custom_data):
+        # alert: Dictionary or String
+        if alert is not None:
+            if not isinstance(alert, six.string_types):
+                cls.check_type(alert, APNsAlert, "alert must be an instance of String or APNsAlert class")
+        # badge: Number
+        cls.check_number("APNsAps.badge", badge)
+        # sound: String
+        cls.check_string("APNsAps.sound", sound)
+        # content_available: number
+        cls.check_number("APNsAps.content_available", content_available)
+        # category: String
+        cls.check_string("APNsAps.category", category)
+        # thread_id: String
+        cls.check_string("APNsAps.thread_id", thread_id)
+        # mutable_content
+        cls.check_boolean("APNsAps.mutable_content", mutable_content)
+        # custom_data
+        if custom_data is not None:
+            if not isinstance(custom_data, dict):
+                raise ValueError('APNsAps.custom_data must be a dict.')
+
+    @classmethod
+    def check_apns_payload_aps_alert(cls, title, body, loc_key, loc_args, title_loc_key, title_loc_args, action_loc_key,
+                                     launch_image, custom_data):
+        # title: String
+        cls.check_string("APNsAlert.title", title)
+        # body: String
+        cls.check_string("APNsAlert.body", body)
+        # loc_key: String
+        cls.check_string("APNsAlert.loc_key", loc_key)
+        # loc_args: Array of strings
+        cls.check_string_list("APNsAlert.loc_args", loc_args)
+        # title_loc_key: String or null
+        cls.check_string("APNsAlert.title_loc_key", title_loc_key)
+        # title_loc_args: Array of strings or null
+        cls.check_string_list("APNsAlert.title_loc_args", title_loc_args)
+        # action_loc_key: String or null
+        cls.check_string("APNsAlert.action_loc_key", action_loc_key)
+        # launch_image: String
+        cls.check_string("APNsAlert.launch_image", launch_image)
+        # custom_data
+        if custom_data is not None:
+            if not isinstance(custom_data, dict):
+                raise ValueError('APNsAlert.custom_data must be a dict.')
+
+    @classmethod
+    def check_apns_hms_options(cls, target_user_type):
+        cls.assert_integer_values("APNsHMSOptions.target_user_type", target_user_type, 1, 2, 3)

+ 224 - 0
Service/HuaweiPushService/push_admin/messaging.py

@@ -0,0 +1,224 @@
+# -*-coding:utf-8-*-
+#
+# Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from Service.HuaweiPushService.push_admin import _messages, _app
+from Service.HuaweiPushService import push_admin
+
+"""HUAWEI Cloud Messaging module."""
+
+""" General Data structure """
+Message = _messages.Message
+Notification = _messages.Notification
+
+""" Web Push related data structure """
+WebPushConfig = _messages.WebPushConfig
+WebPushHeader = _messages.WebPushHeader
+WebPushNotification = _messages.WebPushNotification
+WebPushNotificationAction = _messages.WebPushNotificationAction
+WebPushHMSOptions = _messages.WebPushHMSOptions
+
+""" Android Push related data structure """
+AndroidConfig = _messages.AndroidConfig
+AndroidNotification = _messages.AndroidNotification
+AndroidClickAction = _messages.AndroidClickAction
+AndroidBadgeNotification = _messages.AndroidBadgeNotification
+AndroidLightSettings = _messages.AndroidLightSettings
+AndroidLightSettingsColor = _messages.AndroidLightSettingsColor
+
+""" APNS Push related data structure"""
+APNsConfig = _messages.APNsConfig
+APNsHeader = _messages.APNsHeader
+APNsPayload = _messages.APNsPayload
+APNsAps = _messages.APNsAps
+APNsAlert = _messages.APNsAlert
+APNsHMSOptions = _messages.APNsHMSOptions
+
+"""Common exception definition"""
+ApiCallError = _app.ApiCallError
+
+
+def send_message(message, validate_only=False, app_id=None, verify_peer=False):
+    """
+        Sends the given message Huawei Cloud Messaging (HCM)
+        :param message: An instance of ``messaging.Message``.
+        :param validate_only: A boolean indicating whether to run the operation in dry run mode (optional).
+        :param app_id: app id parameters obtained by developer alliance applying for Push service (optional).
+        :param verify_peer: (optional) Either a boolean, in which case it controls whether we verify
+            the server's TLS certificate, or a string, in which case it must be a path
+            to a CA bundle to use. Defaults to ``True``.
+        :return: SendResponse
+        Raises:
+            ApiCallError: If an error occurs while sending the message to the HCM service.
+    """
+    try:
+        response = push_admin.get_app(app_id).send(message, validate_only, verify_peer=verify_peer)
+        return SendResponse(response)
+    except Exception as e:
+        raise ApiCallError(repr(e))
+
+
+def subscribe_topic(topic, token_list, app_id=None):
+    """
+    :param topic: The specific topic
+    :param token_list: The token list to be added
+    :param app_id: application ID
+    """
+    try:
+        response = push_admin.get_app(app_id).subscribe_topic(topic, token_list)
+        return TopicSubscribeResponse(response)
+    except Exception as e:
+        raise ApiCallError(repr(e))
+
+
+def unsubscribe_topic(topic, token_list, app_id=None):
+    """
+    :param topic: The specific topic
+    :param token_list: The token list to be deleted
+    :param app_id: application ID
+    """
+    try:
+        response = push_admin.get_app(app_id).unsubscribe_topic(topic, token_list)
+        return TopicSubscribeResponse(response)
+    except Exception as e:
+        raise ApiCallError(repr(e))
+
+
+def list_topics(token, app_id=None):
+    """
+    :param token: The token to be queried
+    :param app_id: application ID
+    """
+    try:
+        response = push_admin.get_app(app_id).query_subscribe_list(token)
+        return TopicQueryResponse(response)
+    except Exception as e:
+        raise ApiCallError(repr(e))
+
+
+class SendResponse(object):
+    """
+        The response received from an send request to the HCM API.
+        response: received http response body text from HCM.
+    """
+    def __init__(self, response=None):
+        try:
+            self._code = response['code']
+            self._msg = response['msg']
+            self._requestId = response['requestId']
+        except Exception as e:
+            raise ValueError(format(repr(e)))
+
+    @property
+    def code(self):
+        """errcode"""
+        return self._code
+
+    @property
+    def reason(self):
+        """the description of errcode"""
+        return self._msg
+
+    @property
+    def requestId(self):
+        """A message ID string that uniquely identifies the message."""
+        return self._requestId
+
+
+class BaseTopicResponse(object):
+    """
+    {
+       "msg": "Success",
+       "code": "80000000",
+       "requestId": "157466304904000004000701"
+     }
+    """
+    def __init__(self, json_rsp=None):
+        if json_rsp is None:
+            self._msg = ""
+            self._code = ""
+            self._requestId = ""
+        else:
+            self._msg = json_rsp['msg']
+            self._code = json_rsp['code']
+            self._requestId = json_rsp['requestId']
+
+    @property
+    def msg(self):
+        return self._msg
+
+    @property
+    def code(self):
+        return self._code
+
+    @property
+    def requestId(self):
+        return self._requestId
+
+
+class TopicSubscribeResponse(BaseTopicResponse):
+    """
+     {
+       "msg": "Success",
+       "code": "80000000",
+       "requestId": "157466304904000004000701",
+       "successCount": 2,
+       "failureCount": 0,
+       "errors": []
+     }
+    """
+    def __init__(self, json_rsp=None):
+        super(TopicSubscribeResponse, self).__init__(json_rsp=json_rsp)
+        if json_rsp is None:
+            self._successCount = 0
+            self._failureCount = 0
+            self._errors = []
+        else:
+            self._successCount = json_rsp['successCount']
+            self._failureCount = json_rsp['failureCount']
+            self._errors = json_rsp['errors']
+
+    @property
+    def successCount(self):
+        return self._successCount
+
+    @property
+    def failureCount(self):
+        return self._failureCount
+
+    @property
+    def errors(self):
+        return self._errors
+
+
+class TopicQueryResponse(BaseTopicResponse):
+    """
+         {
+           "msg": "success",
+           "code": "80000000",
+           "requestId": "157466350121600008000701",
+           "topics": [
+                       { "name": "sports",
+                         "addDate": "2019-11-25"
+                         } ]
+         }
+    """
+    def __init__(self, json_rsp=None):
+        super(TopicQueryResponse, self).__init__(json_rsp)
+        self._topics = json_rsp['topics']
+
+    @property
+    def topics(self):
+        return self._topics

+ 433 - 0
Service/PushService.py

@@ -0,0 +1,433 @@
+# -*- coding: utf-8 -*-
+"""
+@Time : 2022/5/19 11:43
+@Auth : Locky
+@File :PushService.py
+@IDE :PyCharm
+"""
+import hashlib
+import json
+import logging
+import os
+import time
+
+import apns2
+import jpush
+import requests
+from pyfcm import FCMNotification
+
+from AnsjerPush.config import APP_BUNDLE_DICT, APNS_MODE, BASE_DIR, APNS_CONFIG, FCM_CONFIG, JPUSH_CONFIG, XMPUSH_CONFIG \
+    , VIVOPUSH_CONFIG, OPPOPUSH_CONFIG, MEIZUPUSH_CONFIG
+from Model.models import UidPushModel
+from Object.RedisObject import RedisObject
+from Service.CommonService import CommonService
+from Service.VivoPushService.push_admin.APIMessage import PushMessage
+from Service.VivoPushService.push_admin.APISender import APISender
+
+
+class PushObject:
+    # 推送对象
+    @staticmethod
+    def get_msg_title(nickname):
+        """
+        获取推送消息标题
+        @param nickname: 设备名
+        @return: msg_title
+        """
+        return nickname
+
+    @staticmethod
+    def get_gateway_msg_text(n_time, tz, lang, alarm):
+        """
+        获取网关推送消息内容
+        @param n_time: 当前时间
+        @param tz: 时区
+        @param lang: 语言
+        @param alarm: 警报
+        @return: msg_text
+        """
+        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
+        if lang == 'cn':
+            msg_text = '{} 日期:{}'.format(alarm, n_date)
+        else:
+            msg_text = '{} date:{}'.format(alarm, n_date)
+        return msg_text
+
+    @staticmethod
+    def get_ai_msg_text(channel, n_time, lang, tz, label):
+        """
+        获取AI推送内容
+        @param channel: 通道
+        @param n_time: 当前时间
+        @param lang: 语言
+        @param tz: 时区
+        @param label: 识别到的标签
+        @return: ai_msg_text
+        """
+        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
+        if lang == 'cn':
+            msg = '摄像头AI识别到了{}'.format(label)
+            ai_msg_text = '{msg} 通道:{channel} 日期:{date}'.format(msg=msg, channel=channel, date=n_date)
+        else:
+            msg = 'Camera AI recognizes {}'.format(label)
+            ai_msg_text = '{msg} channel:{channel} date:{date}'.format(msg=msg, channel=channel, date=n_date)
+        return ai_msg_text
+
+    @staticmethod
+    def get_low_power_msg_text(channel, n_time, lang, tz, electricity, is_sys=0):
+        """
+        获取低电量推送内容
+        @param channel: 通道
+        @param n_time: 当前时间
+        @param lang: 语言
+        @param tz: 时区
+        @param electricity: 电量
+        @param is_sys: 是否为系统消息
+        @return: low_power_msg_text
+        """
+        n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
+        if lang == 'cn':
+            alarm = '剩余电量 ' + electricity
+            if is_sys:
+                low_power_msg_text = '{} 通道:{}'.format(alarm, channel)
+            else:
+                low_power_msg_text = '{} 通道:{} 日期:{}'.format(alarm, channel, n_date)
+        else:
+            alarm = 'Battery remaining ' + electricity
+            if is_sys:
+                low_power_msg_text = '{} channel:{}'.format(alarm, channel)
+            else:
+                low_power_msg_text = '{} channel:{} date:{}'.format(alarm, channel, n_date)
+        return low_power_msg_text
+
+    @staticmethod
+    def ios_apns_push(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text,
+                      uid='', channel='1', launch_image=None):
+        """
+        ios apns 推送
+        @param nickname: 设备昵称
+        @param app_bundle_id: app包id
+        @param token_val: 推送token
+        @param n_time: 当前时间
+        @param event_type: 事件类型
+        @param msg_title: 推送标题
+        @param msg_text: 推送内容
+        @param uid: uid
+        @param channel: 通道
+        @param launch_image: 推送图片链接
+        @return: None
+        """
+        logger = logging.getLogger('info')
+        try:
+            pem_path = os.path.join(BASE_DIR, APNS_CONFIG[app_bundle_id]['pem_path'])
+            logger.info('apns推送app_bundle_id:{}, pem_path:{}'.format(app_bundle_id, pem_path))
+            cli = apns2.APNSClient(mode=APNS_MODE, client_cert=pem_path)
+            alert = apns2.PayloadAlert(title=msg_title, body=msg_text, launch_image=launch_image)
+            push_data = {'alert': 'Motion', 'msg': '', 'sound': '', 'zpush': '1', 'uid': uid, 'channel': channel,
+                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname,
+                         'image_url': launch_image
+                         }
+            payload = apns2.Payload(alert=alert, custom=push_data, sound='default', category='myCategory',
+                                    mutable_content=True)
+            n = apns2.Notification(payload=payload, priority=apns2.PRIORITY_LOW)
+            res = cli.push(n=n, device_token=token_val, topic=app_bundle_id)
+            assert res.status_code == 200
+        except Exception as e:
+            logger.info('--->IOS推送异常{}'.format(repr(e)))
+            return repr(e)
+
+    @staticmethod
+    def android_fcm_push(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text,
+                         uid='', channel='1', image=''):
+        """
+        android fcm 推送
+        @param nickname: 设备昵称
+        @param app_bundle_id: app包id
+        @param token_val: 推送token
+        @param n_time: 当前时间
+        @param event_type: 事件类型
+        @param msg_title: 推送标题
+        @param msg_text: 推送内容
+        @param uid: uid
+        @param channel: 通道
+        @param image: 推送图片链接
+        @return: None
+        """
+        logger = logging.getLogger('info')
+        try:
+            serverKey = FCM_CONFIG[app_bundle_id]
+            push_service = FCMNotification(api_key=serverKey)
+            push_data = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1', 'image': image,
+                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname,
+                         'uid': uid, 'channel': channel
+                         }
+            result = push_service.notify_single_device(registration_id=token_val, message_title=msg_title,
+                                                       message_body=msg_text, data_message=push_data,
+                                                       extra_kwargs={'default_sound': True,
+                                                                     'default_vibrate_timings': True,
+                                                                     'default_light_settings': True,
+                                                                     }
+                                                       )
+            logger.info('fcm推送结果:{}'.format(result))
+        except Exception as e:
+            return repr(e)
+
+    @staticmethod
+    def android_jpush(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text):
+        """
+        android 极光 推送
+        @param nickname: 设备昵称
+        @param app_bundle_id: app包id
+        @param token_val: 推送token
+        @param n_time: 当前时间
+        @param event_type: 事件类型
+        @param msg_title: 推送标题
+        @param msg_text: 推送内容
+        @return: None
+        """
+        try:
+            app_key = JPUSH_CONFIG[app_bundle_id]['Key']
+            master_secret = JPUSH_CONFIG[app_bundle_id]['Secret']
+            # 换成各自的app_key和master_secret
+            _jpush = jpush.JPush(app_key, master_secret)
+            push = _jpush.create_push()
+            push.audience = jpush.registration_id(token_val)
+            push_data = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1',
+                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname
+                         }
+            android = jpush.android(title=msg_title, big_text=msg_text, alert=msg_text, extras=push_data,
+                                    priority=1, style=1, alert_type=7
+                                    )
+            push.notification = jpush.notification(android=android)
+            push.platform = jpush.all_
+            res = push.send()
+            assert res.status_code == 200
+        except Exception as e:
+            return repr(e)
+
+    @staticmethod
+    def android_xmpush(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text,
+                       uid='', channel='1', image=''):
+        """
+        android 小米 推送
+        @param nickname: 设备昵称
+        @param app_bundle_id: app包id
+        @param token_val: 推送token
+        @param n_time: 当前时间
+        @param event_type: 事件类型
+        @param msg_title: 推送标题
+        @param msg_text: 推送内容
+        @param uid: uid
+        @param channel: 通道
+        @param image: 推送图片链接
+        @return: None
+        """
+        logger = logging.getLogger('info')
+        try:
+            url = 'https://api.xmpush.xiaomi.com/v3/message/regid'
+            app_secret = XMPUSH_CONFIG[app_bundle_id]
+            payload = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1',
+                       'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname,
+                       'uid': uid, 'channel': channel
+                       }
+            data = {
+                'title': msg_title,
+                'description': msg_text,
+                'payload': 'payload',
+                'restricted_package_name': app_bundle_id,
+                'registration_id': token_val,
+            }
+            # if image:
+            #     data['extra.notification_style_type'] = 2
+            #     data['extra.notification_bigPic_uri'] = image
+            headers = {
+                'Authorization': 'key={}'.format(app_secret)
+            }
+            response = requests.post(url, data=data, headers=headers)
+            logger.info("小米推送返回值:{}".format(response.json()))
+            assert response.status_code == 200
+        except Exception as e:
+            return repr(e)
+
+    @staticmethod
+    def android_vivopush(token_val, n_time, event_type, msg_title, msg_text, app_bundle_id='', uid='', channel='1',
+                         image='', nickname='', appBundleId=''):
+        """
+        vivo 推送(不支持图片)
+        @param app_bundle_id: app包名
+        @param appBundleId: app包名
+        @param token_val: 推送token
+        @param event_type: 事件类型
+        @param msg_title: 推送标题
+        @param msg_text: 推送内容
+        @param n_time: 当前时间
+        @param nickname: 设备昵称
+        @param uid: uid
+        @param image: 推送图片链接
+        @param channel: 通道
+        @return: None
+        """
+        logger = logging.getLogger('info')
+        try:
+            app_bundle_id = app_bundle_id if app_bundle_id != '' else appBundleId
+            # 获取redis里面的authToken
+            if msg_title == '':
+                msg_title = APP_BUNDLE_DICT[app_bundle_id]
+            app_id = VIVOPUSH_CONFIG[app_bundle_id]['ID']
+            app_key = VIVOPUSH_CONFIG[app_bundle_id]['Key']
+            app_secret = VIVOPUSH_CONFIG[app_bundle_id]['Secret']
+            sender = APISender(app_secret)
+            rec = sender.get_token(app_id, app_key)
+            # 鉴权接口调用获得authToken
+            sender_send = APISender(app_secret)
+            sender_send.set_token(rec['authToken'])
+            push_data = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1', 'image': image,
+                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname,
+                         'uid': uid, 'channel': channel
+                         }
+            #  获取唯一标识符
+            uid_push_qs = UidPushModel.objects.filter(token_val=token_val).values('m_code')
+            m_code = uid_push_qs[0]['m_code'] if uid_push_qs[0]['m_code'] else ''
+            # 推送 push_mode: 推送模式 (0:正式推送;1:测试推送,默认为0)
+            # 推送 event_type: 消息类型 (0:运营类消息,1:系统类消息。默认为 0)
+            # 推送 skip_type: 跳转类型(1:打开 APP 首页 2:打开链接 3:自定义 4:打开 app 内指定页面)
+            message = PushMessage() \
+                .reg_id(token_val) \
+                .title(msg_title) \
+                .content(msg_text) \
+                .push_mode(1) \
+                .notify_type(3) \
+                .skip_type(1) \
+                .request_id(m_code) \
+                .classification(0) \
+                .client_custom_map(**push_data) \
+                .message_dict()
+            rec = sender_send.send(message)
+            logger.info('vivo推送结果:{}'.format(rec))
+            return rec
+        except Exception as e:
+            logger.info('vivo推送异常:{}'.format(e))
+
+    @staticmethod
+    def android_oppopush(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text,
+                         uid='', channel='1', image=''):
+        """
+        android oppo 推送
+        @param nickname: 设备昵称
+        @param app_bundle_id: app包id
+        @param token_val: 推送token
+        @param n_time: 当前时间
+        @param event_type: 事件类型
+        @param msg_title: 推送标题
+        @param msg_text: 推送内容
+        @param uid: uid
+        @param channel: 通道
+        @param image: 推送图片链接
+        @return: None
+        """
+        logger = logging.getLogger('info')
+        try:
+            """
+            android 国内oppo APP消息提醒推送
+            """
+            app_key = OPPOPUSH_CONFIG[app_bundle_id]['Key']
+            master_secret = OPPOPUSH_CONFIG[app_bundle_id]['Secret']
+            url = 'https://api.push.oppomobile.com/'
+            now_time = str(round(time.time() * 1000))
+            # 1、实例化一个sha256对象
+            sha256 = hashlib.sha256()
+            # 2、调用update方法进行加密
+            sha256.update((app_key + now_time + master_secret).encode('utf-8'))
+            # 3、调用hexdigest方法,获取加密结果
+            sign = sha256.hexdigest()
+            # 获取auth_token
+            get_token_url = url + 'server/v1/auth'
+            post_data = {
+                'app_key': app_key,
+                'sign': sign,
+                'timestamp': now_time
+            }
+            headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+            response = requests.post(get_token_url, data=post_data, headers=headers)
+            result = response.json()
+            # 发送推送
+            push_url = url + 'server/v1/message/notification/unicast'
+            extra_data = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1',
+                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname,
+                         'uid': uid, 'channel': channel
+                         }
+            message = {
+                "target_type": 2,
+                "target_value": token_val,
+                "notification": {
+                    "title": msg_title,
+                    "content": msg_text,
+                }
+            }
+            push_data = {
+                'auth_token': result['data']['auth_token'],
+                'message': json.dumps(message)
+            }
+
+            response = requests.post(push_url, data=push_data, headers=headers)
+            logger.info("oppo推送返回值:{}".format(response.json()))
+            assert response.status_code == 200
+        except Exception as e:
+            return repr(e)
+
+    @staticmethod
+    def android_meizupush(token_val, n_time, event_type, msg_title, msg_text, uid='', channel='1',
+                          app_bundle_id='', appBundleId='', nickname='', image=''):
+        """
+        android 魅族推送(不支持图片)
+        @param app_bundle_id: app包名
+        @param appBundleId: app包名
+        @param token_val: 推送token
+        @param event_type: 消息类型 (0:运营类消息,1:系统类消息。默认为 0)
+        @param msg_title: 推送标题
+        @param msg_text: 推送内容
+        @param n_time: 当前时间
+        @param nickname: 设备昵称
+        @param uid: uid
+        @param image: 推送图片链接
+        @param channel: 通道
+        @return: None
+        """
+        logger = logging.getLogger('info')
+        try:
+            #  获取包和AppSecret
+            app_bundle_id = app_bundle_id if app_bundle_id != '' else appBundleId
+            appId = MEIZUPUSH_CONFIG[app_bundle_id]['ID']
+            appSecret = MEIZUPUSH_CONFIG[app_bundle_id]['AppSecret']
+            url = 'https://server-api-push.meizu.com/garcia/api/server/push/varnished/pushByPushId'
+            extra_data = {'alert': 'Motion', 'msg': '', 'sound': 'sound.aif', 'zpush': '1',
+                         'received_at': n_time, 'event_time': n_time, 'event_type': event_type, 'nickname': nickname,
+                         'uid': uid, 'channel': channel
+                         }
+            if msg_title == '':
+                msg_title = APP_BUNDLE_DICT[app_bundle_id]
+            # 拼接发送内容
+            messageJson = '{"clickTypeInfo":{"activity":"","clickType":0,"customAttribute":""},"extra":{},'
+            noticeBarInfo = ('"noticeBarInfo": {"title": "%s", "content":  "%s"},' % (msg_title, msg_text))
+            noticeExpandInfo = '"noticeExpandInfo":{"noticeExpandType":0},"pushTimeInfo":{"validTime":24}}'
+            messageJson += noticeBarInfo
+            messageJson += noticeExpandInfo
+            data_meizu = {
+                'appId': appId,
+                'pushIds': token_val,
+                'messageJson': messageJson
+            }
+            # 魅族MD5加密,生成密钥
+            sign = CommonService.getMD5Sign(data=data_meizu, key=appSecret)
+            data = {
+                'appId': appId,
+                'messageJson': messageJson,
+                'sign': sign,
+                'pushIds': token_val,
+            }
+            # 进行推送
+            response = requests.post(url, data=data)
+            logger.info("魅族推送结果:{}".format(response.json()))
+            return response.status_code
+        except Exception as e:
+            return repr(e)

+ 91 - 0
Service/VivoPushService/push_admin/APIConstants.py

@@ -0,0 +1,91 @@
+# coding=utf-8
+class Constants(object):
+    def __init__(self):
+        pass
+
+    def enum(**self):
+        return type('Enum', (), self)
+
+    __VERSION__ = '1.0'
+    __HTTP_GET__ = 0
+    __HTTP_POST__ = 1
+
+    _METHOD_MAP = {'GET': __HTTP_GET__, 'POST': __HTTP_POST__}
+
+    http_server = "https://api-push.vivo.com.cn"
+
+    '''
+        targetMessage parameter name
+    '''
+
+    http_param_reg_ids = "regIds"
+    http_param_aliases = "aliases"
+    http_param_task_id = "taskId"
+
+    '''
+        pushMessage parameter name
+    '''
+    http_param_tag_expression = "tagExpression"
+    http_param_or_tags = "orTags"
+    http_param_and_tags = "andTags"
+    http_param_not_tags = "notTags"
+    http_param_segment_name = "segmentName"
+    http_param_reg_id = "regId"
+    http_param_content = "content"
+    http_param_title = "title"
+    http_param_ali_as = "alias"
+    http_param_notify_type = "notifyType"
+    http_param_time_to_live = "timeToLive"
+    http_param_skip_type = "skipType"
+    http_param_skip_content = "skipContent"
+    http_param_network_type = "networkType"
+    http_param_client_custom_map = "clientCustomMap"
+    http_param_extra = "extra"
+    http_param_callback = "callback"
+    http_param_callback_param = "callback.param"
+
+    http_param_request_id = "requestId"
+
+    http_param_classification = "classification"
+
+    http_param_push_mode = "pushMode"
+
+
+    http_param_task_ids = "taskIds"
+
+    '''
+        tagMessage parameter name
+    '''
+    http_param_name = "name"
+    http_param_old_name = "oldName"
+    http_param_new_name = "newName"
+    http_param_desc = "desc"
+    http_param_group = "group"
+    http_param_type = "type"
+    http_param_ids = "ids"
+
+    http_param_tag_list = "tagList"
+
+    http_param_expression = "expression"
+
+    request_path = enum(
+        GET_TOKEN=["/message/auth"],
+        PUSH_TO_SINGLE=['/message/send'],
+        SAVE_LIST_PAYLOAD=['/message/saveListPayload '],
+        PUSH_TO_LIST=['/message/pushToList'],
+        PUSH_TO_ALL=['/message/all'],
+        PUSH_TO_TAG=['/message/tagPush'],
+        GET_STATISTICS=['/report/getStatistics'],
+
+        ADD_TAG=['/tag/add'],
+        UPDATE_TAG=['/tag/update'],
+        ADD_MEMBERS=['/tag/addMembers'],
+        REMOVE_MEMBERS=['/tag/removeMembers'],
+
+        ADD_TAG_GROUP=['/tagGroup/add'],
+        UPDATE_TAG_GROUP=['/tagGroup/update'],
+        ADD_TAG_TO_GROUP=['/tagGroup/addToGroup'],
+
+        ADD_TAG_SEGMENT=['/tagSegment/add'],
+        UPDATE_TAG_SEGMENT=['/tagSegment/update'],
+    )

+ 13 - 0
Service/VivoPushService/push_admin/APIError.py

@@ -0,0 +1,13 @@
+
+class APIError(Exception):
+    """
+    raise APIError if receiving json message indicating failure.
+    """
+    def __init__(self, error_code, error, request):
+        self.error_code = error_code
+        self.error = error
+        self.request = request
+        Exception.__init__(self, error)
+
+    def __str__(self):
+        return 'APIError: %s: %s, request: %s' % (self.error_code, self.error, self.request)

+ 103 - 0
Service/VivoPushService/push_admin/APIMessage.py

@@ -0,0 +1,103 @@
+# coding=utf-8
+from Service.VivoPushService.push_admin.APIConstants import Constants
+
+
+class MessageDict(dict):
+    def __getattr__(self, item):
+        try:
+            return self[item]
+        except KeyError:
+            raise AttributeError(r"'message' object has no attribute %s'" % item)
+
+    def __setattr__(self, key, value):
+        self[key] = value
+
+class PushMessage(object):
+    tag_expression={}
+
+    def __init__(self):
+        self.__message_dict = MessageDict()
+
+    def or_tags(self,or_tags):
+        PushMessage.tag_expression[Constants.http_param_or_tags]=or_tags
+        self.__message_dict[Constants.http_param_tag_expression] = PushMessage.tag_expression
+        return self
+
+    def and_tags(self,and_tags):
+        PushMessage.tag_expression[Constants.http_param_and_tags]=and_tags
+        self.__message_dict[Constants.http_param_tag_expression] = PushMessage.tag_expression
+        return self
+
+    def not_tags(self,not_tags):
+        PushMessage.tag_expression[Constants.http_param_not_tags]=not_tags
+        self.__message_dict[Constants.http_param_tag_expression] = PushMessage.tag_expression
+        return self
+
+    def segment_name(self, segment_name):
+        self.__message_dict[Constants.http_param_segment_name] = segment_name
+        self.__message_dict[Constants.http_param_tag_expression] = PushMessage.tag_expression
+        return self
+
+    def reg_id(self, reg_id):
+        self.__message_dict[Constants.http_param_reg_id] = reg_id
+        return self
+
+    def ali_as(self, ali_as):
+        self.__message_dict[Constants.http_param_ali_as] = ali_as
+        return self
+
+    def content(self, content):
+        self.__message_dict[Constants.http_param_content] = content
+        return self
+
+    def title(self, title):
+        self.__message_dict[Constants.http_param_title] = title
+        return self
+
+    def notify_type(self, notify_type):
+        self.__message_dict[Constants.http_param_notify_type] = notify_type
+        return self
+
+    def time_to_live(self, time_to_live):
+        self.__message_dict[Constants.http_param_time_to_live] = time_to_live
+        return self
+
+    def skip_type(self, skip_type):
+        self.__message_dict[Constants.http_param_skip_type] = skip_type
+        return self
+
+    def skip_content(self, skip_content):
+        self.__message_dict[Constants.http_param_skip_content] = skip_content
+        return self
+
+    def network_type(self, network_type=-1):
+        self.__message_dict[Constants.http_param_network_type] = network_type
+        return self
+
+    def request_id(self, request_id):
+        self.__message_dict[Constants.http_param_request_id] = request_id
+        return self
+
+    def classification(self, classification):
+        self.__message_dict[Constants.http_param_classification] = classification
+        return self
+
+    def push_mode(self, push_mode):
+        self.__message_dict[Constants.http_param_push_mode] = push_mode
+        return self
+
+    def client_custom_map(self, **client_custom_map):
+        self.__message_dict[Constants.http_param_client_custom_map] = client_custom_map
+        return self
+
+    def extra(self, value1, value2):
+        extra = {Constants.http_param_callback: value1, Constants.http_param_callback_param: value2}
+        self.__message_dict[Constants.http_param_extra] = extra
+        return self
+
+    '''
+        message params build method
+    '''
+
+    def message_dict(self):
+        return self.__message_dict

+ 73 - 0
Service/VivoPushService/push_admin/APISender.py

@@ -0,0 +1,73 @@
+# coding=utf-8
+import time
+
+from Service.VivoPushService.push_admin.APIConstants import Constants
+from Service.VivoPushService.push_admin.APISenderBase import Base
+from Service.VivoPushService.push_admin.APISignUtil import SignUtil
+
+_BROADCAST_TOPIC_MAX = 5
+_TOPIC_SPLITTER = ';$;'
+
+
+class APISender(Base):
+    """
+    发送消息API(send push message class)
+    构造方法接收两个参数:
+    @:param secret 必填 - APP_SECRET
+    @:param token 可选 - authToken,发送消息时需带该参数以进行鉴权操作,调用鉴权接口获得
+    """
+
+    def get_token(self, app_id, app_key):
+        timestamp = int(round(time.time() * 1000))
+        sign = SignUtil(app_id, app_key, self.secret).sign_util(timestamp)
+        get_token = {'appId': app_id, 'appKey': app_key, 'timestamp': timestamp, 'sign': sign}
+        return self._try_http_request(Constants.request_path.GET_TOKEN, retry_times=3, **get_token)
+
+    def send(self, push_message, retry_times=3):
+        """
+        发送单推消息
+        :param push_message: 消息体(请求参数对象)
+        :param retry_times: 重试次数
+        """
+        return self._try_http_request(Constants.request_path.PUSH_TO_SINGLE, retry_times, **push_message)
+
+    def save_list_payload(self, push_message, retry_times=3):
+        """
+        保存群推消息
+        :param push_message: 消息体(请求参数对象)
+        :param retry_times: 重试次数
+        """
+        return self._try_http_request(Constants.request_path.SAVE_LIST_PAYLOAD, retry_times, **push_message)
+
+    def send_to_list(self, target_push_message, retry_times=3):
+        """
+        推送群推消息
+        :param target_push_message: 消息体(请求参数对象)
+        :param retry_times: 重试次数
+        """
+        return self._try_http_request(Constants.request_path.PUSH_TO_LIST, retry_times, **target_push_message)
+
+    def send_to_all(self, push_message, retry_times=3):
+        """
+        发送全推消息
+        :param push_message: 消息体(请求参数对象)
+        :param retry_times: 重试次数
+        """
+        return self._try_http_request(Constants.request_path.PUSH_TO_ALL, retry_times, **push_message)
+
+    def send_to_tag(self, push_message, retry_times=3):
+        """
+        发送标签推消息
+        :param push_message: 消息体(请求参数对象)
+        :param retry_times: 重试次数
+        """
+        return self._try_http_request(Constants.request_path.PUSH_TO_TAG,retry_times,**push_message)
+
+    def get_statistics(self, taskids_message, retry_times=3):
+        """
+        发送全推消息
+        :param taskids_message: 消息体(请求参数对象)
+        :param retry_times: 重试次数
+        """
+        return self._try_http_request(Constants.request_path.GET_STATISTICS, retry_times, Constants.__HTTP_GET__,
+                                      **taskids_message)

+ 131 - 0
Service/VivoPushService/push_admin/APISenderBase.py

@@ -0,0 +1,131 @@
+import logging
+import time
+import json
+import urllib.request, urllib.parse, urllib.error
+import urllib.request, urllib.error, urllib.parse
+
+from Service.VivoPushService.push_admin.APIConstants import Constants
+from Service.VivoPushService.push_admin.APIError import APIError
+
+_MAX_BACKOFF_DELAY = 1024000
+
+
+class JsonDict(dict):
+    def __getattr__(self, item):
+        try:
+            return self[item]
+        except KeyError:
+            raise AttributeError(r"'JsonDict' object has no attribute %s'" % item)
+
+    def __setattr__(self, key, value):
+        self[key] = value
+
+
+def _parse_json(body):
+    """
+    convert json object to python object
+    :param body: response data
+    """
+    def _obj_hook(pairs):
+        o = JsonDict()
+        for k, v in pairs.items():
+            o[str(k)] = v
+        return o
+
+    return json.loads(body, object_hook=_obj_hook)
+
+
+def _build_request_url(request_path):
+    return Constants.http_server + request_path[0]
+
+
+def _http_call(url, method, token, **message):
+    """
+    :param url: http request url
+    :param method: http request method
+    :param message: params
+    """
+    params = _encode_params(message) if method == Constants.__HTTP_GET__ else ''
+    http_url = '%s?%s' % (url, params) if method == Constants.__HTTP_GET__ else url
+    http_body = None if method == Constants.__HTTP_GET__ else message
+    req = urllib.request.Request(http_url, data=json.dumps(http_body).encode("utf-8"))
+    if token:
+        req.add_header('authToken', token)
+    req.add_header('Content-Type', 'application/json;charset=UTF-8')
+    try:
+        resp = urllib.request.urlopen(req, timeout=5)
+        r = _parse_json(resp.read().decode())
+        return r
+    except urllib.error.URLError as e:
+        raise APIError('-5', e.reason, 'http error ' + str(e.code))
+    except BaseException as e:
+        raise e
+
+def _encode_params(kw):
+    """
+    splic get request url
+    :param kw: params
+    """
+    args = ''
+    s = ''
+    for k, v in kw.items():
+        for t in v:
+            s = s+ str(t) + ','
+        args = '%s=%s' %(k, s[:-1])
+    return args
+
+class Base(object):
+    def __init__(self, secret, token=None):
+        self.secret = secret
+        self.token = token
+
+    def set_token(self, token):
+        self.token = token
+
+    def _http_request(self, request_path, method, **message):
+        """
+        :param request_path: http interface
+        :param method: GET|POST
+        :param message: params
+        """
+        request_url = _build_request_url(request_path)
+        try:
+            ret = _http_call(request_url, method, self.token, **message)
+            return ret
+        except APIError as ex:
+            logging.error("%s request: [%s] error [%s]" % (Constants.http_server, request_url, ex))
+            raise ex
+
+    def http_post(self, request_path, **message):
+        logging.info("POST %s" % request_path[0])
+        return self._http_request(request_path, Constants.__HTTP_POST__, **message)
+
+    def http_get(self, request_path, **message):
+        logging.info("GET %s" % request_path[0])
+        return self._http_request(request_path, Constants.__HTTP_GET__, **message)
+
+    def _try_http_request(self, request_path, retry_times, method=Constants.__HTTP_POST__, **message):
+        is_fail, try_time, result, sleep_time = True, 0, None, 1
+        while is_fail and try_time < retry_times:
+            try:
+                if method == Constants.__HTTP_POST__:
+                    result = self.http_post(request_path, **message)
+                elif method == Constants.__HTTP_GET__:
+                    result = self.http_get(request_path, **message)
+                else:
+                    raise APIError('-2', 'not support %s http request' % method, 'http error')
+                is_fail = False
+            except APIError as ex:
+                '''
+                    failure retry
+                '''
+                if ex.error_code == '-5':
+                    is_fail = True
+                try_time += 1
+                logging.error('code:[%s] - description:[%s] - reason:[%s] - try_time:[%s]' % (ex.error_code, ex.error, ex.request, try_time))
+                time.sleep(sleep_time)
+                if 2 * sleep_time < _MAX_BACKOFF_DELAY:
+                    sleep_time *= 2
+        if not result:
+            raise APIError('-3', 'retry %s time failure' % retry_times, 'request error')
+        return result

+ 17 - 0
Service/VivoPushService/push_admin/APISignUtil.py

@@ -0,0 +1,17 @@
+# coding=utf-8
+import  hashlib
+
+
+class SignUtil:
+    def __init__(self, app_id, app_key, app_secret):
+        self.app_id = app_id
+        self.app_key = app_key
+        self.app_secret = app_secret
+
+    def sign_util(self, timestamp):
+        m = hashlib.md5()
+        m.update(self.app_id.encode("UTF-8"))
+        m.update(self.app_key.encode("UTF-8"))
+        m.update(str(timestamp).encode("UTF-8"))
+        m.update(self.app_secret.encode("UTF-8"))
+        return m.hexdigest()

+ 0 - 0
Service/VivoPushService/push_admin/__init__.py


+ 1 - 1
cn_formal_manage.py

@@ -10,7 +10,7 @@ if __name__ == '__main__':
     if arg_m == 'migrate':
         print('do not migrate')
         exit()
-    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.cn_formal_settings')
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.cn_config.cn_formal_settings')
     try:
         from django.core.management import execute_from_command_line
     except ImportError as exc:

+ 1 - 1
eur_formal_manage.py

@@ -10,7 +10,7 @@ if __name__ == '__main__':
     if arg_m == 'migrate':
         print('do not migrate')
         exit()
-    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.eur_formal_settings')
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.eur_config.eur_formal_settings')
     try:
         from django.core.management import execute_from_command_line
     except ImportError as exc:

+ 1 - 1
formal_manage.py

@@ -10,7 +10,7 @@ if __name__ == '__main__':
     if arg_m == 'migrate':
         print('do not migrate')
         exit()
-    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.formal_settings')
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.us_config.formal_settings')
     try:
         from django.core.management import execute_from_command_line
     except ImportError as exc:

+ 1 - 1
local_manage.py

@@ -10,7 +10,7 @@ if __name__ == '__main__':
     if arg_m == 'migrate':
         print('do not migrate')
         exit()
-    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.local_settings')
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.dev_config.local_settings')
     try:
         from django.core.management import execute_from_command_line
     except ImportError as exc:

+ 68 - 216
requirements.txt

@@ -1,224 +1,76 @@
-alabaster==0.7.12
-anaconda-client==1.7.2
-anaconda-navigator==1.9.7
-anaconda-project==0.8.3
+aliyun-python-sdk-core==2.13.36
+aliyun-python-sdk-core-v3==2.13.33
+aliyun-python-sdk-kms==2.16.0
+apns2-client==0.5.4
 asgiref==3.2.3
-asn1crypto==1.0.1
-astroid==2.3.1
-astropy==3.2.1
-atomicwrites==1.3.0
-attrs==19.2.0
-Babel==2.7.0
-backcall==0.1.0
-backports.functools-lru-cache==1.5
-backports.os==0.1.1
-backports.shutil-get-terminal-size==1.0.0
-backports.tempfile==1.0
-backports.weakref==1.0.post1
-beautifulsoup4==4.8.0
-bitarray==1.0.1
-bkcharts==0.2
-bleach==3.1.0
-bokeh==1.3.4
-boto==2.49.0
-Bottleneck==1.2.1
-certifi==2019.9.11
-cffi==1.12.3
-chardet==3.0.4
-Click==7.0
-cloudpickle==1.2.2
-clyent==1.2.2
-colorama==0.4.1
-comtypes==1.1.7
-conda==4.7.12
-conda-build==3.18.9
-conda-package-handling==1.6.0
-conda-verify==3.4.2
-contextlib2==0.6.0
-cryptography==2.7
-cycler==0.10.0
-Cython==0.29.13
-cytoolz==0.10.0
-dask==2.5.2
-decorator==4.4.0
-defusedxml==0.6.0
-distributed==2.5.2
+asn1crypto==0.24.0
+blinker==1.4
+boto3==1.11.16
+botocore==1.14.17
+certifi==2022.12.7
+cffi==1.15.1
+chardet==5.1.0
+charset-normalizer==2.0.12
+configobj==5.0.6
+crcmod==1.7
+cryptography==39.0.2
 Django==3.0.3
-django-appconf==1.0.3
+django-appconf==1.0.5
 django-cors-headers==3.2.1
-django-imagekit==4.0.2
-django-tinymce==2.8.0
+django-imagekit==4.1.0
 docutils==0.15.2
-entrypoints==0.3
-et-xmlfile==1.0.1
-fastcache==1.1.0
-filelock==3.0.12
-Flask==1.1.1
-fsspec==0.5.2
-future==0.17.1
-gevent==1.4.0
-glob2==0.7
-greenlet==0.4.15
-gunicorn==20.0.4
-h5py==2.9.0
-HeapDict==1.0.1
-html5lib==1.0.1
-idna==2.8
-imageio==2.6.0
-imagesize==1.1.0
-importlib-metadata==0.23
-ipykernel==5.1.2
-ipython==7.8.0
-ipython-genutils==0.2.0
-ipywidgets==7.5.1
-isort==4.3.21
-itsdangerous==1.1.0
-jdcal==1.4.1
-jedi==0.15.1
-Jinja2==2.10.3
-joblib==0.13.2
-json5==0.8.5
-jsonschema==3.0.2
-jupyter==1.0.0
-jupyter-client==5.3.3
-jupyter-console==6.0.0
-jupyter-core==4.5.0
-jupyterlab==1.1.4
-jupyterlab-server==1.0.6
-keyring==18.0.0
-kiwisolver==1.1.0
-lazy-object-proxy==1.4.2
-libarchive-c==2.8
-llvmlite==0.29.0
-locket==0.2.0
-lxml==4.4.1
-MarkupSafe==1.1.1
-matplotlib==3.1.1
-mccabe==0.6.1
-menuinst==1.4.16
-mistune==0.8.4
-mkl-fft==1.0.14
-mkl-random==1.1.0
-mkl-service==2.3.0
-mock==3.0.5
-more-itertools==7.2.0
-mpmath==1.1.0
-msgpack==0.6.1
-multipledispatch==0.6.0
+grpcio==1.52.0
+h2==2.6.2
+hpack==3.0.0
+httplib2==0.9.2
+hyper==0.7.0
+hyperframe==3.2.0
+idna==3.4
+ipip-ipdb==1.3.2
+jmespath==0.9.4
+jpush==3.3.8
+jsonpatch==1.16
+jsonpointer==1.10
+keyrings.alt==3.0
 mysqlclient==1.4.6
-navigator-updater==0.2.1
-nbconvert==5.6.0
-nbformat==4.4.0
-networkx==2.3
-nltk==3.4.5
-nose==1.3.7
-notebook==6.0.1
-numba==0.45.1
-numexpr==2.7.0
-numpy==1.16.5
-numpydoc==0.9.1
-olefile==0.46
-openpyxl==3.0.0
-packaging==19.2
-pandas==0.25.1
-pandocfilters==1.4.2
-parso==0.5.1
-partd==1.0.0
-path.py==12.0.1
-pathlib2==2.3.5
-patsy==0.5.1
-pep8==1.7.1
-pickleshare==0.7.5
+numpy==1.21.6
+oauthlib==2.0.6
+oss2==2.9.1
+packaging==21.3
+paypalrestsdk==1.13.1
+pendulum==2.1.2
+pexpect==4.2.1
 pilkit==2.0
-Pillow==6.2.0
-pkginfo==1.5.0.1
-pluggy==0.13.0
-ply==3.11
-prometheus-client==0.7.1
-prompt-toolkit==2.0.10
-psutil==5.6.3
-py==1.8.0
-pycodestyle==2.5.0
-pycosat==0.6.3
-pycparser==2.19
-pycrypto==2.6.1
-pycurl==7.43.0.3
-pyflakes==2.1.1
-Pygments==2.4.2
-pylint==2.4.2
-PyMySQL==0.9.3
-pyodbc==4.0.27
-pyOpenSSL==19.0.0
-pyparsing==2.4.2
-pyreadline==2.1
-pyrsistent==0.15.4
-PySocks==1.7.1
-pytest==5.2.1
-pytest-arraydiff==0.3
-pytest-astropy==0.5.0
-pytest-doctestplus==0.4.0
-pytest-openfiles==0.4.0
-pytest-remotedata==0.3.2
-python-dateutil==2.8.0
+Pillow==9.4.0
+protobuf==3.19.6
+ptyprocess==0.7.0
+pyasn1==0.4.8
+pycparser==2.21
+pycryptodome==3.17
+pyfcm==1.4.7
+pyipip==0.1.1
+PyJWT==1.5.3
+pyOpenSSL==23.0.0
+pyparsing==3.0.9
+pyserial==3.4
+python-dateutil==2.8.1
+python-debian==0.1.32
+python-rapidjson==1.9
 pytz==2019.3
-PyWavelets==1.0.3
-pywin32==223
-pywinpty==0.5.5
-PyYAML==5.1.2
-pyzmq==18.1.0
-QtAwesome==0.6.0
-qtconsole==4.5.5
-QtPy==1.9.0
-requests==2.22.0
-rope==0.14.0
-ruamel-yaml==0.15.46
-scikit-image==0.15.0
-scikit-learn==0.21.3
-scipy==1.3.1
-seaborn==0.9.0
-Send2Trash==1.5.0
-simplegeneric==0.8.1
-singledispatch==3.4.0.3
-six==1.12.0
-snowballstemmer==2.0.0
-sortedcollections==1.1.2
-sortedcontainers==2.1.0
-soupsieve==1.9.3
-Sphinx==2.2.0
-sphinxcontrib-applehelp==1.0.1
-sphinxcontrib-devhelp==1.0.1
-sphinxcontrib-htmlhelp==1.0.2
-sphinxcontrib-jsmath==1.0.1
-sphinxcontrib-qthelp==1.0.2
-sphinxcontrib-serializinghtml==1.1.3
-sphinxcontrib-websupport==1.1.2
-spyder==3.3.6
-spyder-kernels==0.5.2
-SQLAlchemy==1.3.9
+pytzdata==2020.1
+pyxdg==0.25
+redis==3.4.1
+requests==2.28.2
+requests-unixsocket==0.1.5
+s3transfer==0.3.7
+SecretStorage==2.3.1
+simplejson==3.17.0
+six==1.11.0
 sqlparse==0.3.0
-statsmodels==0.10.1
-sympy==1.4
-tables==3.5.2
-tblib==1.4.0
-terminado==0.8.2
-testpath==0.4.2
-toolz==0.10.0
-tornado==6.0.3
-tqdm==4.36.1
-traitlets==4.3.3
-unicodecsv==0.14.1
-urllib3==1.24.2
-wcwidth==0.1.7
-webencodings==0.5.1
-Werkzeug==1.0.0
-widgetsnbextension==3.5.1
-win-inet-pton==1.1.0
-win-unicode-console==0.5
-wincertstore==0.2
-wrapt==1.11.2
-xlrd==1.2.0
-XlsxWriter==1.2.1
-xlwings==0.15.10
-xlwt==1.3.0
-zict==1.0.0
-zipp==0.6.0
+ssh-import-id==5.7
+supervisor==4.1.0
+tritonclient==2.30.0
+typing_extensions==4.5.0
+ujson==5.7.0
+urllib3==1.25.11
+var-dump==1.2

+ 1 - 1
test_manage.py

@@ -10,7 +10,7 @@ if __name__ == '__main__':
     if arg_m == 'migrate':
         print('do not migrate')
         exit()
-    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.test_settings')
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AnsjerPush.test_config.test_settings')
     try:
         from django.core.management import execute_from_command_line
     except ImportError as exc: