mirror of
				https://github.com/jumpserver/jumpserver.git
				synced 2025-10-31 05:41:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			281 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			281 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import traceback
 | |
| from html2text import HTML2Text
 | |
| from typing import Iterable
 | |
| from itertools import chain
 | |
| import textwrap
 | |
| 
 | |
| from celery import shared_task
 | |
| from django.utils.translation import gettext_lazy as _
 | |
| 
 | |
| from common.utils.timezone import local_now
 | |
| from common.utils import lazyproperty
 | |
| from settings.utils import get_login_title
 | |
| from users.models import User
 | |
| from notifications.backends import BACKEND
 | |
| from .models import SystemMsgSubscription, UserMsgSubscription
 | |
| 
 | |
| __all__ = ('SystemMessage', 'UserMessage', 'system_msgs', 'Message')
 | |
| 
 | |
| 
 | |
| system_msgs = []
 | |
| user_msgs = []
 | |
| 
 | |
| 
 | |
| class MessageType(type):
 | |
|     def __new__(cls, name, bases, attrs: dict):
 | |
|         clz = type.__new__(cls, name, bases, attrs)
 | |
| 
 | |
|         if 'message_type_label' in attrs \
 | |
|                 and 'category' in attrs \
 | |
|                 and 'category_label' in attrs:
 | |
|             message_type = clz.get_message_type()
 | |
| 
 | |
|             msg = {
 | |
|                 'message_type': message_type,
 | |
|                 'message_type_label': attrs['message_type_label'],
 | |
|                 'category': attrs['category'],
 | |
|                 'category_label': attrs['category_label'],
 | |
|             }
 | |
|             if issubclass(clz, SystemMessage):
 | |
|                 system_msgs.append(msg)
 | |
|             elif issubclass(clz, UserMessage):
 | |
|                 user_msgs.append(msg)
 | |
| 
 | |
|         return clz
 | |
| 
 | |
| 
 | |
| @shared_task
 | |
| def publish_task(msg):
 | |
|     msg.publish()
 | |
| 
 | |
| 
 | |
| class Message(metaclass=MessageType):
 | |
|     """
 | |
|     这里封装了什么?
 | |
|         封装不同消息的模板,提供统一的发送消息的接口
 | |
|         - publish 该方法的实现与消息订阅的表结构有关
 | |
|         - send_msg
 | |
|     """
 | |
|     message_type_label: str
 | |
|     category: str
 | |
|     category_label: str
 | |
|     text_msg_ignore_links = True
 | |
| 
 | |
|     @classmethod
 | |
|     def get_message_type(cls):
 | |
|         return cls.__name__
 | |
| 
 | |
|     def publish_async(self):
 | |
|         return publish_task.delay(self)
 | |
| 
 | |
|     @classmethod
 | |
|     def gen_test_msg(cls):
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     def publish(self):
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     def send_msg(self, users: Iterable, backends: Iterable = BACKEND):
 | |
|         backends = set(backends)
 | |
|         backends.add(BACKEND.SITE_MSG)  # 站内信必须发
 | |
| 
 | |
|         for backend in backends:
 | |
|             try:
 | |
|                 backend = BACKEND(backend)
 | |
|                 if not backend.is_enable:
 | |
|                     continue
 | |
|                 get_msg_method = getattr(self, f'get_{backend}_msg', self.get_common_msg)
 | |
|                 msg = get_msg_method()
 | |
|                 client = backend.client()
 | |
|                 client.send_msg(users, **msg)
 | |
|             except NotImplementedError:
 | |
|                 continue
 | |
|             except:
 | |
|                 traceback.print_exc()
 | |
| 
 | |
|     @classmethod
 | |
|     def send_test_msg(cls, ding=True, wecom=False):
 | |
|         msg = cls.gen_test_msg()
 | |
|         if not msg:
 | |
|             return
 | |
| 
 | |
|         from users.models import User
 | |
|         users = User.objects.filter(username='admin')
 | |
|         backends = []
 | |
|         if ding:
 | |
|             backends.append(BACKEND.DINGTALK)
 | |
|         if wecom:
 | |
|             backends.append(BACKEND.WECOM)
 | |
|         msg.send_msg(users, backends)
 | |
| 
 | |
|     @staticmethod
 | |
|     def get_common_msg() -> dict:
 | |
|         return {'subject': '', 'message': ''}
 | |
| 
 | |
|     def get_html_msg(self) -> dict:
 | |
|         return self.get_common_msg()
 | |
| 
 | |
|     def get_markdown_msg(self) -> dict:
 | |
|         h = HTML2Text()
 | |
|         h.body_width = 300
 | |
|         msg = self.get_html_msg()
 | |
|         content = msg['message']
 | |
|         msg['message'] = h.handle(content)
 | |
|         return msg
 | |
| 
 | |
|     def get_text_msg(self) -> dict:
 | |
|         h = HTML2Text()
 | |
|         h.body_width = 90
 | |
|         msg = self.get_html_msg()
 | |
|         content = msg['message']
 | |
|         h.ignore_links = self.text_msg_ignore_links
 | |
|         msg['message'] = h.handle(content)
 | |
|         return msg
 | |
| 
 | |
|     @lazyproperty
 | |
|     def common_msg(self) -> dict:
 | |
|         return self.get_common_msg()
 | |
| 
 | |
|     @lazyproperty
 | |
|     def text_msg(self) -> dict:
 | |
|         msg = self.get_text_msg()
 | |
|         return msg
 | |
| 
 | |
|     @lazyproperty
 | |
|     def markdown_msg(self):
 | |
|         return self.get_markdown_msg()
 | |
| 
 | |
|     @lazyproperty
 | |
|     def html_msg(self) -> dict:
 | |
|         msg = self.get_html_msg()
 | |
|         return msg
 | |
| 
 | |
|     @lazyproperty
 | |
|     def html_msg_with_sign(self):
 | |
|         msg = self.get_html_msg()
 | |
|         msg['message'] = textwrap.dedent("""
 | |
|         {}
 | |
|         <small>
 | |
|         <br />
 | |
|         —
 | |
|         <br />
 | |
|         {}
 | |
|         </small>
 | |
|         """).format(msg['message'], self.signature)
 | |
|         return msg
 | |
| 
 | |
|     @lazyproperty
 | |
|     def text_msg_with_sign(self):
 | |
|         msg = self.get_text_msg()
 | |
|         msg['message'] = textwrap.dedent("""
 | |
|         {}
 | |
|         —
 | |
|         {}
 | |
|         """).format(msg['message'], self.signature)
 | |
|         return msg
 | |
| 
 | |
|     @lazyproperty
 | |
|     def signature(self):
 | |
|         return get_login_title()
 | |
| 
 | |
|     # --------------------------------------------------------------
 | |
|     # 支持不同发送消息的方式定义自己的消息内容,比如有些支持 html 标签
 | |
|     def get_dingtalk_msg(self) -> dict:
 | |
|         # 钉钉相同的消息一天只能发一次,所以给所有消息添加基于时间的序号,使他们不相同
 | |
|         message = self.markdown_msg['message']
 | |
|         time = local_now().strftime('%Y-%m-%d %H:%M:%S')
 | |
|         suffix = '\n{}: {}'.format(_('Time'), time)
 | |
| 
 | |
|         return {
 | |
|             'subject': self.markdown_msg['subject'],
 | |
|             'message': message + suffix
 | |
|         }
 | |
| 
 | |
|     def get_wecom_msg(self) -> dict:
 | |
|         return self.markdown_msg
 | |
| 
 | |
|     def get_feishu_msg(self) -> dict:
 | |
|         return self.text_msg
 | |
| 
 | |
|     def get_email_msg(self) -> dict:
 | |
|         return self.html_msg_with_sign
 | |
| 
 | |
|     def get_site_msg_msg(self) -> dict:
 | |
|         return self.html_msg
 | |
| 
 | |
|     def get_sms_msg(self) -> dict:
 | |
|         return self.text_msg_with_sign
 | |
| 
 | |
|     @classmethod
 | |
|     def get_all_sub_messages(cls):
 | |
|         def get_subclasses(cls):
 | |
|             """returns all subclasses of argument, cls"""
 | |
|             if issubclass(cls, type):
 | |
|                 subclasses = cls.__subclasses__(cls)
 | |
|             else:
 | |
|                 subclasses = cls.__subclasses__()
 | |
|             for subclass in subclasses:
 | |
|                 subclasses.extend(get_subclasses(subclass))
 | |
|             return subclasses
 | |
| 
 | |
|         messages_cls = get_subclasses(cls)
 | |
|         return messages_cls
 | |
| 
 | |
|     @classmethod
 | |
|     def test_all_messages(cls, ding=True, wecom=False):
 | |
|         messages_cls = cls.get_all_sub_messages()
 | |
| 
 | |
|         for _cls in messages_cls:
 | |
|             try:
 | |
|                 _cls.send_test_msg(ding=ding, wecom=wecom)
 | |
|             except NotImplementedError:
 | |
|                 continue
 | |
| 
 | |
| 
 | |
| class SystemMessage(Message):
 | |
|     def publish(self):
 | |
|         subscription = SystemMsgSubscription.objects.get(
 | |
|             message_type=self.get_message_type()
 | |
|         )
 | |
| 
 | |
|         # 只发送当前有效后端
 | |
|         receive_backends = subscription.receive_backends
 | |
|         receive_backends = BACKEND.filter_enable_backends(receive_backends)
 | |
| 
 | |
|         users = [
 | |
|             *subscription.users.all(),
 | |
|             *chain(*[g.users.all() for g in subscription.groups.all()])
 | |
|         ]
 | |
|         self.send_msg(users, receive_backends)
 | |
| 
 | |
|     @classmethod
 | |
|     def post_insert_to_db(cls, subscription: SystemMsgSubscription):
 | |
|         pass
 | |
| 
 | |
|     @classmethod
 | |
|     def gen_test_msg(cls):
 | |
|         raise NotImplementedError
 | |
| 
 | |
| 
 | |
| class UserMessage(Message):
 | |
|     user: User
 | |
| 
 | |
|     def __init__(self, user):
 | |
|         self.user = user
 | |
| 
 | |
|     def publish(self):
 | |
|         """
 | |
|         发送消息到每个用户配置的接收方式上
 | |
|         """
 | |
|         sub = UserMsgSubscription.objects.get(user=self.user)
 | |
|         self.send_msg([self.user], sub.receive_backends)
 | |
| 
 | |
|     @classmethod
 | |
|     def get_test_user(cls):
 | |
|         from users.models import User
 | |
|         return User.objects.all().first()
 | |
| 
 | |
|     @classmethod
 | |
|     def gen_test_msg(cls):
 | |
|         raise NotImplementedError
 |