فهرست منبع

定制化推送接口

locky 1 سال پیش
والد
کامیت
46862dc259

+ 14 - 0
AnsjerPush/test_config/test_settings.py

@@ -224,6 +224,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': {
@@ -246,6 +255,11 @@ LOGGING = {
             'handlers': ['v1_push'],
             'level': 'INFO',
             'propagate': False
+        },
+        'customized_push': {
+            'handlers': ['customized_push'],
+            'level': 'INFO',
+            'propagate': False
         }
     }
 }

+ 3 - 2
AnsjerPush/urls.py

@@ -1,7 +1,7 @@
 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
 
 urlpatterns = [
@@ -15,5 +15,6 @@ urlpatterns = [
     re_path(r'init/(?P<operation>.*)', InitController.InitView.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())
+    re_path('unicom/device/combo/(?P<operation>.*)$', ComboCronPushController.ComboCronPushView.as_view()),
+    re_path('customized_push/(?P<operation>.*)', CustomizedPushController.CustomizedPushView.as_view())
 ]

+ 75 - 0
Controller/CustomizedPushController.py

@@ -0,0 +1,75 @@
+# @Author    : Rocky
+# @File      : CustomizedPushController.py
+# @Time      : 2023/10/18 16:31
+import logging
+import threading
+
+from django.http import HttpResponse, JsonResponse
+from django.views import View
+
+from Model.models import Device_Info, SceneLog, CustomizedPush, Device_User
+from Object.RedisObject import RedisObject
+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:
+            redis_obj = RedisObject()
+            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']
+            }
+            # 异步推送消息和保存数据
+            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)

+ 22 - 0
Model/models.py

@@ -2538,3 +2538,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 = '定制化推送'

+ 11 - 0
Service/CommonService.py

@@ -317,3 +317,14 @@ class CommonService:
             content = content.decode('utf-8')
             content = content[i:-i]
         return content
+
+    @staticmethod
+    def timestamp_to_str(timestamp):
+        """
+        时间戳转时间字符串
+        @param timestamp: 时间戳
+        @return time_str: 时间字符串
+        """
+        struct_time = time.localtime(timestamp)
+        time_str = time.strftime("%Y-%m-%d %H:%M:%S", struct_time)
+        return time_str

+ 108 - 0
Service/CustomizedPushService.py

@@ -0,0 +1,108 @@
+# @Author    : Rocky
+# @File      : CustomizedPushService.py
+# @Time      : 2023/10/19 15:49
+import logging
+import time
+
+from Model.models import DeviceTypeModel, Device_Info, GatewayPush, CountryModel, SysMsgModel
+from Service.CommonService import CommonService
+from Service.HuaweiPushService.HuaweiPushService import HuaweiPushObject
+from Service.PushService import PushObject
+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())
+        n, m = register_period[:1], register_period[-1:]
+        if m == '-':
+            # 0-,所有时间
+            if n == '0':
+                user_id_list = device_info_qs.distinct('userID_id').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)
+                # 注册时间越小越早
+                user_id_list = device_info_qs.filter(userID__data_joined__lte=n_year_ago).\
+                    distinct('userID_id').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
+            user_id_list = device_info_qs.\
+                filter(userID__data_joined__gte=m_year_ago, userID__data_joined__lte=n_year_ago).\
+                distinct('userID_id').values_list('userID_id', flat=True)
+
+        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']
+        icon_link = kwargs['icon_link'] if kwargs['icon_link'] != '' else None
+
+        # 推送
+        n_time = int(time.time())
+        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']
+        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')
+        for gateway_push in gateway_push_qs:
+            push_succeed = False
+            # ios
+            if gateway_push['push_type'] == 0:
+                push_succeed = PushObject.ios_apns_push(nickname='', app_bundle_id=gateway_push['app_bundle_id'],
+                                                        token_val=gateway_push['token_val'], n_time=n_time,
+                                                        event_type=0, msg_title=title, msg_text=msg,
+                                                        launch_image=icon_link)
+            # gcm
+            elif gateway_push['push_type'] == 1:
+                if icon_link is None:
+                    icon_link = ''
+                push_succeed = PushObject.android_fcm_push(nickname='', app_bundle_id=gateway_push['app_bundle_id'],
+                                                           token_val=gateway_push['token_val'], n_time=n_time,
+                                                           event_type=0, msg_title=title, msg_text=msg, image=icon_link)
+            # 华为
+            elif gateway_push['push_type'] == 3:
+                huawei_push_object = HuaweiPushObject()
+                push_succeed = huawei_push_object.\
+                    send_push_notify_message(token_val=gateway_push['token_val'], msg_title=title,
+                                             msg_text=msg, n_time=n_time, image_url=icon_link)
+
+            # 推送成功,写入系统消息
+            if push_succeed:
+                SysMsgModel.objects.create(userID_id=gateway_push_qs['user_id'], title=title, msg=msg, addTime=n_time,
+                                           updTime=n_time)
+        CUSTOMIZED_PUSH_LOGGER.info('customized_push_id:{}推送完成'.format(kwargs['id']))

+ 8 - 3
Service/HuaweiPushService/HuaweiPushService.py

@@ -32,16 +32,19 @@ class HuaweiPushObject:
         @param channel: 通道
         @param app_bundle_id: APP包id
         @param appBundleId: APP包id
-        @return:
+        @return: bool
         """
         LOGGER.info(
             '华为推送参数: uid:{}, token_val:{}, msg_title:{}, msg_text:{}, image_url:{}, event_type:{}, n_time:{}'.format(
                 uid, token_val, msg_title, msg_text, image_url, event_type, n_time))
 
-        self.send_notify_message(msg_title, msg_text, image_url, uid, nickname, event_type, n_time, token_val)
+        send_succeed = self.send_notify_message(msg_title, msg_text, image_url, uid,
+                                               nickname, event_type, n_time, token_val)
         if int(event_type) in [606, 607]:
             self.send_data_message(uid, event_type, n_time, token_val)
 
+        return send_succeed
+
     def send_notify_message(self, msg_title, msg_text, image_url, uid, nickname, event_type, n_time, token_val):
         """
         发送通知推送
@@ -53,7 +56,7 @@ class HuaweiPushObject:
         @param event_type:
         @param n_time:
         @param token_val:
-        @return: None
+        @return: bool
         """
         LOGGER.info('{}进入发送通知推送函数'.format(uid))
         msg_title = '设备昵称: {}'.format(msg_title)
@@ -101,8 +104,10 @@ class HuaweiPushObject:
             response = messaging.send_message(message, verify_peer=certifi.where())
             LOGGER.info('{}华为通知推送响应: {}'.format(uid, json.dumps(vars(response))))
             assert (response.code == '80000000')
+            return True
         except Exception as e:
             LOGGER.info('华为通知推送异常: {}'.format(repr(e)))
+            return False
 
     @staticmethod
     def send_data_message(uid, event_type, n_time, token_val):

+ 6 - 13
Service/PushService.py

@@ -117,7 +117,7 @@ class PushObject:
         @param uid: uid
         @param channel: 通道
         @param launch_image: 推送图片链接
-        @return: None
+        @return: bool
         """
         pem_path = os.path.join(BASE_DIR, APNS_CONFIG[app_bundle_id]['pem_path'])
         LOGGER.info('IOS推送: app_bundle_id:{}, pem_path:{}'.format(app_bundle_id, pem_path))
@@ -134,19 +134,10 @@ class PushObject:
             res = cli.push(n=n, device_token=token_val, topic=app_bundle_id)
             LOGGER.info('IOS推送响应状态码{},params,uid:{},{}'.format(res.status_code, uid, json.dumps(push_data)))
             assert res.status_code == 200
-        except (AssertionError, ConnectionResetError) as e:
-            LOGGER.info('IOS推送异常: {}, 证书路径: {}'.format(repr(e), pem_path))
+            return True
         except Exception as e:
             LOGGER.info('IOS推送异常: {}, 证书路径: {}'.format(repr(e), pem_path))
-
-            # 限制每小时发送一次
-            redis_obj = RedisObject()
-            key = 'ios_push_error_mail_{}'.format(app_bundle_id)
-            time_limit = redis_obj.get_data(key)
-            if not time_limit:
-                redis_obj.set_data(key, 1, 60 * 60)
-                email_content = '{}服IOS推送异常: {}, 证书路径: {}'.format(CONFIG_INFO, repr(e), pem_path)
-                S3Email().send_email(email_content, 'servers@ansjer.com')
+            return False
 
     @staticmethod
     def android_fcm_push(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text,
@@ -163,7 +154,7 @@ class PushObject:
         @param uid: uid
         @param channel: 通道
         @param image: 推送图片链接
-        @return: None
+        @return: bool
         """
         try:
             serverKey = FCM_CONFIG[app_bundle_id]
@@ -181,8 +172,10 @@ class PushObject:
                                                                      },
                                                        )
             LOGGER.info('fcm推送结果:{}'.format(result))
+            return True
         except Exception as e:
             LOGGER.info('fcm推送异常:{}'.format(repr(e)))
+            return False
 
     @staticmethod
     def android_jpush(nickname, app_bundle_id, token_val, n_time, event_type, msg_title, msg_text, channel=1):