Browse Source

定制化推送

locky 1 năm trước cách đây
mục cha
commit
8515492418

+ 14 - 0
AnsjerPush/cn_config/cn_formal_settings.py

@@ -225,6 +225,15 @@ LOGGING = {
             'formatter': 'standard',
             'encoding': 'utf-8',
         },
+        'customized_push': {
+            'level': 'INFO',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': BASE_DIR + '/static/log/customized_push/info.log',
+            'backupCount': 10,
+            'maxBytes': 1024 * 1024 * 2 * 100,  # 100M
+            'formatter': 'standard',
+            'encoding': 'utf-8',
+        },
     },
     'loggers': {
         'django': {
@@ -247,6 +256,11 @@ LOGGING = {
             'handlers': ['v1_push'],
             'level': 'INFO',
             'propagate': False
+        },
+        'customized_push': {
+            'handlers': ['customized_push'],
+            'level': 'INFO',
+            'propagate': False
         }
     }
 }

+ 14 - 0
AnsjerPush/eur_config/eur_formal_settings.py

@@ -219,6 +219,15 @@ LOGGING = {
             'formatter': 'standard',
             'encoding': 'utf-8',
         },
+        'customized_push': {
+            'level': 'INFO',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': BASE_DIR + '/static/log/customized_push/info.log',
+            'backupCount': 10,
+            'maxBytes': 1024 * 1024 * 2 * 100,  # 100M
+            'formatter': 'standard',
+            'encoding': 'utf-8',
+        },
     },
     'loggers': {
         'django': {
@@ -241,6 +250,11 @@ LOGGING = {
             'handlers': ['v1_push'],
             'level': 'INFO',
             'propagate': False
+        },
+        'customized_push': {
+            'handlers': ['customized_push'],
+            'level': 'INFO',
+            'propagate': False
         }
     }
 }

+ 3 - 2
AnsjerPush/urls.py

@@ -1,12 +1,13 @@
 from django.urls import path, re_path
 
 from Controller import DetectController, ShadowController, DetectControllerV2, AiController, gatewayController, \
-    PowerWarningController, InitController
+    PowerWarningController, InitController, CustomizedPushController
 from Controller.ComboCron import ComboCronPushController
 from Controller.Cron import CronTaskController
 
 urlpatterns = [
     path('deviceShadow/generateUTK', ShadowController.generate_utk),
+    path('deviceShadow/decryptETK', ShadowController.decrypt_etk),
     path('deviceShadow/update', ShadowController.update_device_shadow),
 
     path('notify/push', DetectController.NotificationView.as_view()),
@@ -17,6 +18,6 @@ urlpatterns = [
     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()),
-
+    re_path('customized_push/(?P<operation>.*)', CustomizedPushController.CustomizedPushView.as_view()),
     re_path('cron/create/(?P<operation>.*)', CronTaskController.CronTaskView.as_view()),
 ]

+ 1 - 2
AnsjerPush/us_config/formal_config.py

@@ -1,7 +1,6 @@
 """
 独立于config.py的配置文件
 """
-import os
 import datetime
 
 # 配置信息
@@ -21,7 +20,7 @@ SECRET_ACCESS_KEY = 'ckYLg4Lo9ZXJIcJEAKkzf2rWvs8Xth1FCjqiAqUw'
 PUSH_BUCKET = 'foreignpush'                                # 推送存储桶
 
 # redis节点
-REDIS_ADDRESS = 'amaaaaaayszequiamxr7cdpparig3ptmytvde5vvnz6n7gceo4232sbhhlsa-p.redis.us-phoenix-1.oci.oraclecloud.com'
+REDIS_ADDRESS = 'pushredis.5tgle2.0001.usw1.cache.amazonaws.com'
 
 APNS_MODE = 'prod'
 

+ 15 - 1
AnsjerPush/us_config/formal_settings.py

@@ -226,6 +226,15 @@ LOGGING = {
             'formatter': 'standard',
             'encoding': 'utf-8',
         },
+        'customized_push': {
+            'level': 'INFO',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': BASE_DIR + '/static/log/customized_push/info.log',
+            'backupCount': 10,
+            'maxBytes': 1024 * 1024 * 2 * 100,  # 100M
+            'formatter': 'standard',
+            'encoding': 'utf-8',
+        },
     },
     'loggers': {
         'django': {
@@ -249,5 +258,10 @@ LOGGING = {
             'level': 'INFO',
             'propagate': False
         }
-    }
+    },
+        'customized_push': {
+            'handlers': ['customized_push'],
+            'level': 'INFO',
+            'propagate': False
+        }
 }

+ 74 - 0
Controller/CustomizedPushController.py

@@ -0,0 +1,74 @@
+# @Author    : Rocky
+# @File      : CustomizedPushController.py
+# @Time      : 2023/10/18 16:31
+import logging
+import threading
+
+from django.http import HttpResponse
+from django.views import View
+
+from Model.models import CustomizedPush
+from Object.ResponseObject import ResponseObject
+from Service.CustomizedPushService import CustomizedPushObject
+
+CUSTOMIZED_PUSH_LOGGER = logging.getLogger('customized_push')
+
+
+class CustomizedPushView(View):
+
+    def get(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.GET, operation)
+
+    def post(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.POST, operation)
+
+    def validation(self, request_dict, operation):
+        response = ResponseObject()
+        if operation == 'start':  # 开始定制化推送
+            return self.customized_push_start(request_dict, response)
+
+    @staticmethod
+    def customized_push_start(request_dict, response):
+        customized_push_id = request_dict.get('customized_push_id', None)
+        if not customized_push_id:
+            return response.json(444)
+        try:
+            customized_push_qs = CustomizedPush.objects.filter(id=customized_push_id, push_satus=False).values(
+                'title', 'msg', 'link', 'icon_link', 'country', 'device_type', 'register_period', 'push_app')
+            if not customized_push_qs.exists():
+                return response.json(173)
+            kwargs = {
+                'id': customized_push_id,
+                'title': customized_push_qs[0]['title'],
+                'msg': customized_push_qs[0]['msg'],
+                'link': customized_push_qs[0]['link'],
+                'icon_link': customized_push_qs[0]['icon_link'],
+                'country': customized_push_qs[0]['country'],
+                'device_type': customized_push_qs[0]['device_type'],
+                'register_period': customized_push_qs[0]['register_period'],
+                'push_app': customized_push_qs[0]['push_app']
+            }
+            # customized_push(**kwargs)
+            # 异步推送消息和保存数据
+            push_thread = threading.Thread(
+                target=customized_push,
+                kwargs=kwargs)
+            push_thread.start()
+
+            # 更新推送状态
+            customized_push_qs.update(push_satus=True)
+            return response.json(0)
+        except Exception as e:
+            return HttpResponse(repr(e), status=500)
+
+
+def customized_push(**kwargs):
+    CUSTOMIZED_PUSH_LOGGER.info('customized_push_id:{}开始推送,kwargs:{}'.format(kwargs['id'], kwargs))
+    user_id_list = CustomizedPushObject.query_push_user(kwargs['device_type'], kwargs['country'],
+                                                        kwargs['register_period'])
+    kwargs['user_id_list'] = user_id_list
+    CustomizedPushObject.push_and_save_sys_msg(**kwargs)

+ 26 - 0
Controller/ShadowController.py

@@ -11,6 +11,7 @@ from Model.models import Device_Info, UidSetModel, UID_Preview, VoicePromptModel
 from Object.ETkObject import ETkObject
 from Object.ResponseObject import ResponseObject
 from Service.CommonService import CommonService
+from Service.DevicePushService import DevicePushService
 
 
 def generate_utk(request):
@@ -45,6 +46,31 @@ def generate_utk(request):
         return response.json(444, 'username password')
 
 
+def decrypt_etk(request):
+    """
+    解密etk或uid_token
+    @param request: 请求
+    @request etk:
+    @request uid_token:
+    @return : uid
+    """
+    request.encoding = 'utf-8'
+    response = ResponseObject()
+    if request.method == 'GET':
+        request_dict = request.GET
+    elif request.method == 'POST':
+        request_dict = request.POST
+    else:
+        return response.json(444, 'wrong method')
+
+    etk = request_dict.get('etk', None)
+    uid_token = request_dict.get('uid_token', None)
+    if not any([etk, uid_token]):
+        return response.json(444, 'uid')
+    uid = DevicePushService.decode_uid(etk, uid_token)
+    return response.json(0, {'uid': uid})
+
+
 def update_device_shadow(request):
     """
     设备生成或更新(复位时)设备影子

+ 27 - 2
Model/models.py

@@ -1696,11 +1696,14 @@ class UID_Preview(models.Model):
 class SysMsgModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name='自增id')
     userID_id = models.CharField(default='', db_index=True, blank=True, max_length=32, verbose_name=u'用户ID')
-    msg = models.TextField(blank=True, default='', verbose_name=u'发送内容')
+    title = models.CharField(default='', max_length=64, verbose_name='标题')
+    msg = models.TextField(blank=True, default='', verbose_name='发送内容')
     status = models.SmallIntegerField(verbose_name='是否已读', default=0)  # 0:否,1:是
     addTime = models.IntegerField(verbose_name='添加时间', default=0)
     updTime = models.IntegerField(verbose_name='更新时间', default=0)
-    eventType = models.IntegerField(verbose_name='消息类型', default=0)  # 默认系统消息类型,0系统消息,1 ipcamera消息
+    # 消息类型, 0:系统通知, 702:休眠, 704:低电量
+    eventType = models.IntegerField(verbose_name='消息类型', default=0)
+    jumpLink = models.TextField(default='', verbose_name='跳转链接')
     uid = models.CharField(default='', max_length=20, db_index=True, verbose_name='设备UID')
 
     class Meta:
@@ -3116,3 +3119,25 @@ class SocketInfo(models.Model):
         db_table = 's_socket_info'
         verbose_name = '插座信息'
         verbose_name_plural = verbose_name
+
+
+class CustomizedPush(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name='主键')
+    title = models.CharField(default='', max_length=64, verbose_name='标题')
+    msg = models.TextField(default='', verbose_name='内容')
+    link = models.TextField(default='', verbose_name='链接')
+    icon_link = models.TextField(default='', verbose_name='预览图链接')
+    country = models.CharField(default='', max_length=32, verbose_name='国家')
+    # 多选型号用,分开
+    device_type = models.TextField(default='', verbose_name='设备类型')
+    register_period = models.CharField(default='', max_length=32, verbose_name='注册年限')
+    time_zone = models.CharField(default='', max_length=8, verbose_name='时区')
+    push_time = models.CharField(default='', max_length=32, verbose_name='推送时间')
+    push_timestamp = models.IntegerField(default=0, verbose_name='推送时间戳')
+    push_app = models.CharField(default='', max_length=64, verbose_name='推送APP')
+    # False:待推送, True:已推送
+    push_satus = models.BooleanField(default=False, verbose_name='推送状态')
+
+    class Meta:
+        db_table = 'customized_push'
+        verbose_name = '定制化推送'

+ 22 - 10
Object/RedisObject.py

@@ -1,21 +1,24 @@
-import redis,ssl
+import redis
 from AnsjerPush.config import REDIS_ADDRESS, CONFIG_INFO, CONFIG_US
 
 # 本地调试把注释打开
 # REDIS_ADDRESS = '127.0.0.1'
 
+
 class RedisObject:
 
     def __init__(self, db=0):
-        if CONFIG_INFO != CONFIG_US:
-            self.POOL = redis.ConnectionPool(host=REDIS_ADDRESS, port=6379, db=db)
-            self.CONN = redis.Redis(connection_pool=self.POOL)
-        else:
-            self.CONN = redis.StrictRedis(
-                host=REDIS_ADDRESS,
-                ssl=True,
-                ssl_cert_reqs=None,
-            )
+        self.POOL = redis.ConnectionPool(host=REDIS_ADDRESS, port=6379, db=db)
+        self.CONN = redis.Redis(connection_pool=self.POOL)
+        # if CONFIG_INFO != CONFIG_US:
+        #     self.POOL = redis.ConnectionPool(host=REDIS_ADDRESS, port=6379, db=db)
+        #     self.CONN = redis.Redis(connection_pool=self.POOL)
+        # else:
+        #     self.CONN = redis.StrictRedis(
+        #         host=REDIS_ADDRESS,
+        #         ssl=True,
+        #         ssl_cert_reqs=None,
+        #     )
 
     def set_data(self, key, val, expire=0):
         try:
@@ -79,3 +82,12 @@ class RedisObject:
         @return : bool
         """
         return self.CONN.ltrim(name, start, end)
+
+    def lindex(self, name, index=-1):
+        """
+        根据下标查找元素
+        @param name: 列表名称
+        @param index: 区间结束下标, -1:最后一个元素
+        @return : bool
+        """
+        return self.CONN.lindex(name, index)

+ 192 - 0
Service/CustomizedPushService.py

@@ -0,0 +1,192 @@
+# @Author    : Rocky
+# @File      : CustomizedPushService.py
+# @Time      : 2023/10/19 15:49
+import logging
+import threading
+import time
+
+from Model.models import DeviceTypeModel, Device_Info, GatewayPush, CountryModel, SysMsgModel
+from Object.RedisObject import RedisObject
+from Service.CommonService import CommonService
+from Service.HuaweiPushService.HuaweiPushService import HuaweiPushObject
+from Service.PushService import PushObject
+from AnsjerPush.config import XM_PUSH_CHANNEL_ID
+CUSTOMIZED_PUSH_LOGGER = logging.getLogger('customized_push')
+
+
+class CustomizedPushObject:
+
+    @staticmethod
+    def query_push_user(device_name, country, register_period):
+        """
+        查询需要推送的用户id列表
+        @param device_name: 设备型号
+        @param country: 国家
+        @param register_period: 用户注册年限
+        @return: uid_id_list
+        """
+        # 设备型号和国家
+        device_name_list = device_name.split(',')
+        device_type_list = DeviceTypeModel.objects.filter(name__in=device_name_list).values_list('type', flat=True)
+        country_qs = CountryModel.objects.filter(country_name=country).values('id')
+        country_id = country_qs[0]['id']
+        device_info_qs = Device_Info.objects.filter(Type__in=device_type_list, userID__region_country=country_id)
+        # 获取时间范围
+        now_time = int(time.time())
+
+        index = register_period.find('-')
+        n, m = register_period[:index], register_period[index+1:]
+        if m == '':
+            # 0-,所有时间
+            if n == '0':
+                device_info_qs = device_info_qs.values_list('userID_id', flat=True)
+            # n-,n年以上
+            else:
+                # n年前时间戳转时间字符串
+                n_years_seconds = int(n) * 365 * 24 * 60 * 60
+                n_year_ago_timestamp = now_time - n_years_seconds
+                n_year_ago = CommonService.timestamp_to_str(n_year_ago_timestamp)
+                # 注册时间越小越早
+                device_info_qs = device_info_qs.filter(userID__data_joined__lte=n_year_ago).\
+                    values_list('userID_id', flat=True)
+        else:
+            # n-m年,(如2-3年)
+            n_years_seconds, m_years_seconds = int(n) * 365 * 24 * 60 * 60, int(m) * 365 * 24 * 60 * 60
+            n_year_ago_timestamp = now_time - n_years_seconds
+            m_year_ago_timestamp = now_time - m_years_seconds
+            # 时间戳转时间字符串
+            n_year_ago = CommonService.timestamp_to_str(n_year_ago_timestamp)   # 2021
+            m_year_ago = CommonService.timestamp_to_str(m_year_ago_timestamp)   # 2020
+            # 2020 <= 注册时间 <= 2021
+            device_info_qs = device_info_qs.\
+                filter(userID__data_joined__gte=m_year_ago, userID__data_joined__lte=n_year_ago).\
+                values_list('userID_id', flat=True)
+
+        user_id_list = list(device_info_qs)
+
+        return user_id_list
+
+    @classmethod
+    def push_and_save_sys_msg(cls, **kwargs):
+        """
+        推送和保存系统消息
+        @param kwargs: 参数
+        @return:
+        """
+        user_id_list = kwargs['user_id_list']
+        title = kwargs['title']
+        msg = kwargs['msg']
+        link = kwargs['link']
+        icon_link = kwargs['icon_link'] if kwargs['icon_link'] != '' else None
+
+        n_time = int(time.time())
+        push_kwargs = {
+            'n_time': n_time,
+            'title': title,
+            'msg': msg,
+            'link': link,
+            'icon_link': icon_link
+        }
+
+        # 推送
+        if kwargs['push_app'] == 'ZosiSmart':
+            app_bundle_id_list = ['com.ansjer.zccloud_a', 'com.ansjer.zccloud']
+        else:
+            app_bundle_id_list = ['com.ansjer.zccloud_ab', 'com.ansjer.customizede']
+
+        try:
+            gateway_push_qs = GatewayPush.objects.filter(user_id__in=user_id_list, app_bundle_id__in=app_bundle_id_list).\
+                values('user_id', 'app_bundle_id', 'push_type', 'token_val')
+            if gateway_push_qs.exists():
+                sys_msg_list = []
+                saved_user_id_list = []
+                for gateway_push in gateway_push_qs:
+                    # user_id保存列表,避免重复写入数据
+                    user_id = gateway_push['user_id']
+                    if user_id not in saved_user_id_list:
+                        saved_user_id_list.append(user_id)
+                        sys_msg_list.append(SysMsgModel(
+                            userID_id=user_id, title=title, msg=msg, jumpLink=link, addTime=n_time, updTime=n_time))
+
+                    # 异步推送消息
+                    push_kwargs['gateway_push'] = gateway_push
+                    push_thread = threading.Thread(
+                        target=cls.start_push,
+                        kwargs=push_kwargs)
+                    push_thread.start()
+
+                SysMsgModel.objects.bulk_create(sys_msg_list)
+                CUSTOMIZED_PUSH_LOGGER.info('customized_push_id:{}推送完成'.format(kwargs['id']))
+        except Exception as e:
+            CUSTOMIZED_PUSH_LOGGER.info('定制化推送或保存数据异常,'
+                                        'error_line:{},error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    @classmethod
+    def start_push(cls, **kwargs):
+        gateway_push = kwargs['gateway_push']
+        title = kwargs['title']
+        n_time = kwargs['n_time']
+        msg = kwargs['msg']
+        link = kwargs['link']
+        icon_link = kwargs['icon_link']
+
+        push_type = gateway_push['push_type']
+        user_id = gateway_push['user_id']
+        app_bundle_id = gateway_push['app_bundle_id']
+        token_val = gateway_push['token_val']
+
+        push_succeed = cls.push_msg(push_type, app_bundle_id, token_val, n_time, title, msg, icon_link)
+
+        push_status = '成功' if push_succeed else '失败'
+        CUSTOMIZED_PUSH_LOGGER.info('{}推送{},push_type:{}'.format(user_id, push_status, push_type))
+
+    @staticmethod
+    def push_msg(push_type, app_bundle_id, token_val, n_time, title, msg, icon_link):
+        push_kwargs = {
+            'nickname': '',
+            'event_type': 0,
+            'app_bundle_id': app_bundle_id,
+            'token_val': token_val,
+            'msg_title': title,
+            'msg_text': msg,
+            'n_time': n_time,
+        }
+        try:
+            # ios
+            if push_type == 0:
+                push_kwargs['launch_image'] = icon_link
+                return PushObject.ios_apns_push(**push_kwargs)
+            # gcm
+            elif push_type == 1:
+                if icon_link is None:
+                    icon_link = ''
+                push_kwargs['image'] = icon_link
+                return PushObject.android_fcm_push(**push_kwargs)
+            # 极光
+            elif push_type == 2:
+                push_succeed = PushObject.android_jpush(**push_kwargs)
+            # 华为
+            elif push_type == 3:
+                push_kwargs['image_url'] = icon_link
+                huawei_push_object = HuaweiPushObject()
+                return huawei_push_object.send_push_notify_message(**push_kwargs)
+            # 小米
+            elif push_type == 4:
+                push_kwargs['channel_id'] = XM_PUSH_CHANNEL_ID['service_reminder']
+                return PushObject.android_xmpush(**push_kwargs)
+            # vivo
+            elif push_type == 5:
+                return PushObject.android_vivopush(**push_kwargs)
+            # oppo
+            elif push_type == 6:
+                push_kwargs['channel_id'] = 'VALUE_ADDED'
+                return PushObject.android_oppopush(**push_kwargs)
+            # 魅族
+            elif push_type == 7:
+                return PushObject.android_meizupush(**push_kwargs)
+            else:
+                return False
+        except Exception as e:
+            CUSTOMIZED_PUSH_LOGGER.info('定制化推送异常,'
+                                        'error_line:{},error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+            return False