mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-01-29 21:51:31 +00:00
feat: 添加短信服务和用户消息通知
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
from django.http import Http404
|
||||
from rest_framework.mixins import ListModelMixin, UpdateModelMixin
|
||||
from rest_framework.mixins import ListModelMixin, UpdateModelMixin, RetrieveModelMixin
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
from common.drf.api import JMSGenericViewSet
|
||||
from common.permissions import IsObjectOwner, IsSuperUser, OnlySuperUserCanList
|
||||
from notifications.notifications import system_msgs
|
||||
from notifications.models import SystemMsgSubscription
|
||||
from notifications.models import SystemMsgSubscription, UserMsgSubscription
|
||||
from notifications.backends import BACKEND
|
||||
from notifications.serializers import (
|
||||
SystemMsgSubscriptionSerializer, SystemMsgSubscriptionByCategorySerializer
|
||||
SystemMsgSubscriptionSerializer, SystemMsgSubscriptionByCategorySerializer,
|
||||
UserMsgSubscriptionSerializer,
|
||||
)
|
||||
|
||||
__all__ = ('BackendListView', 'SystemMsgSubscriptionViewSet')
|
||||
__all__ = ('BackendListView', 'SystemMsgSubscriptionViewSet', 'UserMsgSubscriptionViewSet')
|
||||
|
||||
|
||||
class BackendListView(APIView):
|
||||
@@ -70,3 +70,13 @@ class SystemMsgSubscriptionViewSet(ListModelMixin,
|
||||
|
||||
serializer = self.get_serializer(data, many=True)
|
||||
return Response(data=serializer.data)
|
||||
|
||||
|
||||
class UserMsgSubscriptionViewSet(ListModelMixin,
|
||||
RetrieveModelMixin,
|
||||
UpdateModelMixin,
|
||||
JMSGenericViewSet):
|
||||
lookup_field = 'user_id'
|
||||
queryset = UserMsgSubscription.objects.all()
|
||||
serializer_class = UserMsgSubscriptionSerializer
|
||||
permission_classes = (IsObjectOwner | IsSuperUser, OnlySuperUserCanList)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import importlib
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
|
||||
from .dingtalk import DingTalk
|
||||
from .email import Email
|
||||
from .site_msg import SiteMessage
|
||||
from .wecom import WeCom
|
||||
from .feishu import FeiShu
|
||||
client_name_mapper = {}
|
||||
|
||||
|
||||
class BACKEND(models.TextChoices):
|
||||
@@ -14,17 +12,11 @@ class BACKEND(models.TextChoices):
|
||||
DINGTALK = 'dingtalk', _('DingTalk')
|
||||
SITE_MSG = 'site_msg', _('Site message')
|
||||
FEISHU = 'feishu', _('FeiShu')
|
||||
SMS = 'sms', _('SMS')
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
client = {
|
||||
self.EMAIL: Email,
|
||||
self.WECOM: WeCom,
|
||||
self.DINGTALK: DingTalk,
|
||||
self.SITE_MSG: SiteMessage,
|
||||
self.FEISHU: FeiShu,
|
||||
}[self]
|
||||
return client
|
||||
return client_name_mapper[self]
|
||||
|
||||
def get_account(self, user):
|
||||
return self.client.get_account(user)
|
||||
@@ -37,3 +29,8 @@ class BACKEND(models.TextChoices):
|
||||
def filter_enable_backends(cls, backends):
|
||||
enable_backends = [b for b in backends if cls(b).is_enable]
|
||||
return enable_backends
|
||||
|
||||
|
||||
for b in BACKEND:
|
||||
m = importlib.import_module(f'.{b}', __package__)
|
||||
client_name_mapper[b] = m.backend
|
||||
|
||||
@@ -14,6 +14,9 @@ class DingTalk(BackendBase):
|
||||
agentid=settings.DINGTALK_AGENTID
|
||||
)
|
||||
|
||||
def send_msg(self, users, msg):
|
||||
def send_msg(self, users, message, subject=None):
|
||||
accounts, __, __ = self.get_accounts(users)
|
||||
return self.dingtalk.send_text(accounts, msg)
|
||||
return self.dingtalk.send_text(accounts, message)
|
||||
|
||||
|
||||
backend = DingTalk
|
||||
|
||||
@@ -8,7 +8,10 @@ class Email(BackendBase):
|
||||
account_field = 'email'
|
||||
is_enable_field_in_settings = 'EMAIL_HOST_USER'
|
||||
|
||||
def send_msg(self, users, subject, message):
|
||||
def send_msg(self, users, message, subject):
|
||||
from_email = settings.EMAIL_FROM or settings.EMAIL_HOST_USER
|
||||
accounts, __, __ = self.get_accounts(users)
|
||||
send_mail(subject, message, from_email, accounts, html_message=message)
|
||||
|
||||
|
||||
backend = Email
|
||||
|
||||
@@ -14,6 +14,9 @@ class FeiShu(BackendBase):
|
||||
app_secret=settings.FEISHU_APP_SECRET
|
||||
)
|
||||
|
||||
def send_msg(self, users, msg):
|
||||
def send_msg(self, users, message, subject=None):
|
||||
accounts, __, __ = self.get_accounts(users)
|
||||
return self.client.send_text(accounts, msg)
|
||||
return self.client.send_text(accounts, message)
|
||||
|
||||
|
||||
backend = FeiShu
|
||||
|
||||
@@ -5,10 +5,13 @@ from .base import BackendBase
|
||||
class SiteMessage(BackendBase):
|
||||
account_field = 'id'
|
||||
|
||||
def send_msg(self, users, subject, message):
|
||||
def send_msg(self, users, message, subject):
|
||||
accounts, __, __ = self.get_accounts(users)
|
||||
Client.send_msg(subject, message, user_ids=accounts)
|
||||
|
||||
@classmethod
|
||||
def is_enable(cls):
|
||||
return True
|
||||
|
||||
|
||||
backend = SiteMessage
|
||||
|
||||
25
apps/notifications/backends/sms.py
Normal file
25
apps/notifications/backends/sms.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from django.conf import settings
|
||||
|
||||
from common.message.backends.sms.alibaba import AlibabaSMS as Client
|
||||
from .base import BackendBase
|
||||
|
||||
|
||||
class SMS(BackendBase):
|
||||
account_field = 'phone'
|
||||
is_enable_field_in_settings = 'AUTH_SMS'
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
暂时只对接阿里,之后再扩展
|
||||
"""
|
||||
self.client = Client(
|
||||
access_key_id=settings.ALIBABA_ACCESS_KEY_ID,
|
||||
access_key_secret=settings.ALIBABA_ACCESS_KEY_SECRET
|
||||
)
|
||||
|
||||
def send_msg(self, users, sign_name: str, template_code: str, template_param: dict):
|
||||
accounts, __, __ = self.get_accounts(users)
|
||||
return self.client.send_sms(accounts, sign_name, template_code, template_param)
|
||||
|
||||
|
||||
backend = SMS
|
||||
@@ -15,6 +15,9 @@ class WeCom(BackendBase):
|
||||
agentid=settings.WECOM_AGENTID
|
||||
)
|
||||
|
||||
def send_msg(self, users, msg):
|
||||
def send_msg(self, users, message, subject=None):
|
||||
accounts, __, __ = self.get_accounts(users)
|
||||
return self.wecom.send_text(accounts, msg)
|
||||
return self.wecom.send_text(accounts, message)
|
||||
|
||||
|
||||
backend = WeCom
|
||||
|
||||
25
apps/notifications/migrations/0002_auto_20210823_1619.py
Normal file
25
apps/notifications/migrations/0002_auto_20210823_1619.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 3.1.12 on 2021-08-23 08:19
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('notifications', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='usermsgsubscription',
|
||||
name='message_type',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usermsgsubscription',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_msg_subscriptions', to=settings.AUTH_USER_MODEL, unique=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 3.1.12 on 2021-08-23 07:52
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def init_user_msg_subscription(apps, schema_editor):
|
||||
UserMsgSubscription = apps.get_model('notifications', 'UserMsgSubscription')
|
||||
User = apps.get_model('users', 'User')
|
||||
|
||||
to_create = []
|
||||
users = User.objects.all()
|
||||
for user in users:
|
||||
receive_backends = []
|
||||
|
||||
receive_backends.append('site_msg')
|
||||
|
||||
if user.email:
|
||||
receive_backends.append('email')
|
||||
|
||||
if user.wecom_id:
|
||||
receive_backends.append('wecom')
|
||||
|
||||
if user.dingtalk_id:
|
||||
receive_backends.append('dingtalk')
|
||||
|
||||
if user.feishu_id:
|
||||
receive_backends.append('feishu')
|
||||
|
||||
to_create.append(UserMsgSubscription(user=user, receive_backends=receive_backends))
|
||||
UserMsgSubscription.objects.bulk_create(to_create)
|
||||
print(f'\n Init user message subscription: {len(to_create)}')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0036_user_feishu_id'),
|
||||
('notifications', '0002_auto_20210823_1619'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(init_user_msg_subscription)
|
||||
]
|
||||
@@ -6,12 +6,11 @@ __all__ = ('SystemMsgSubscription', 'UserMsgSubscription')
|
||||
|
||||
|
||||
class UserMsgSubscription(JMSModel):
|
||||
message_type = models.CharField(max_length=128)
|
||||
user = models.ForeignKey('users.User', related_name='user_msg_subscriptions', on_delete=models.CASCADE)
|
||||
user = models.ForeignKey('users.User', unique=True, related_name='user_msg_subscriptions', on_delete=models.CASCADE)
|
||||
receive_backends = models.JSONField(default=list)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.message_type}'
|
||||
return f'{self.user} subscription: {self.receive_backends}'
|
||||
|
||||
|
||||
class SystemMsgSubscription(JMSModel):
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
from typing import Iterable
|
||||
import traceback
|
||||
from itertools import chain
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db.utils import ProgrammingError
|
||||
from celery import shared_task
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from users.models import User
|
||||
from notifications.backends import BACKEND
|
||||
from .models import SystemMsgSubscription
|
||||
from .models import SystemMsgSubscription, UserMsgSubscription
|
||||
|
||||
__all__ = ('SystemMessage', 'UserMessage')
|
||||
|
||||
@@ -69,37 +71,49 @@ class Message(metaclass=MessageType):
|
||||
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()
|
||||
|
||||
try:
|
||||
msg = get_msg_method()
|
||||
except NotImplementedError:
|
||||
continue
|
||||
|
||||
client = backend.client()
|
||||
|
||||
if isinstance(msg, dict):
|
||||
client.send_msg(users, **msg)
|
||||
else:
|
||||
client.send_msg(users, msg)
|
||||
client.send_msg(users, **msg)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
def get_common_msg(self) -> str:
|
||||
def get_common_msg(self) -> dict:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_dingtalk_msg(self) -> str:
|
||||
@lazyproperty
|
||||
def common_msg(self) -> dict:
|
||||
return self.get_common_msg()
|
||||
|
||||
def get_wecom_msg(self) -> str:
|
||||
return self.get_common_msg()
|
||||
# --------------------------------------------------------------
|
||||
# 支持不同发送消息的方式定义自己的消息内容,比如有些支持 html 标签
|
||||
def get_dingtalk_msg(self) -> dict:
|
||||
return self.common_msg
|
||||
|
||||
def get_wecom_msg(self) -> dict:
|
||||
return self.common_msg
|
||||
|
||||
def get_feishu_msg(self) -> dict:
|
||||
return self.common_msg
|
||||
|
||||
def get_email_msg(self) -> dict:
|
||||
msg = self.get_common_msg()
|
||||
subject = f'{msg[:80]} ...' if len(msg) >= 80 else msg
|
||||
return {
|
||||
'subject': subject,
|
||||
'message': msg
|
||||
}
|
||||
return self.common_msg
|
||||
|
||||
def get_site_msg_msg(self) -> dict:
|
||||
return self.get_email_msg()
|
||||
return self.common_msg
|
||||
|
||||
def get_sms_msg(self) -> dict:
|
||||
raise NotImplementedError
|
||||
# --------------------------------------------------------------
|
||||
|
||||
|
||||
class SystemMessage(Message):
|
||||
@@ -125,4 +139,16 @@ class SystemMessage(Message):
|
||||
|
||||
|
||||
class UserMessage(Message):
|
||||
pass
|
||||
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)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.drf.serializers import BulkModelSerializer
|
||||
from notifications.models import SystemMsgSubscription
|
||||
from notifications.models import SystemMsgSubscription, UserMsgSubscription
|
||||
|
||||
|
||||
class SystemMsgSubscriptionSerializer(BulkModelSerializer):
|
||||
@@ -27,3 +27,11 @@ class SystemMsgSubscriptionByCategorySerializer(serializers.Serializer):
|
||||
category = serializers.CharField()
|
||||
category_label = serializers.CharField()
|
||||
children = SystemMsgSubscriptionSerializer(many=True)
|
||||
|
||||
|
||||
class UserMsgSubscriptionSerializer(BulkModelSerializer):
|
||||
receive_backends = serializers.ListField(child=serializers.CharField(), read_only=False)
|
||||
|
||||
class Meta:
|
||||
model = UserMsgSubscription
|
||||
fields = ('user_id', 'receive_backends',)
|
||||
|
||||
@@ -6,14 +6,14 @@ from django.utils.functional import LazyObject
|
||||
from django.db.models.signals import post_save
|
||||
from django.db.models.signals import post_migrate
|
||||
from django.dispatch import receiver
|
||||
from django.db.utils import DEFAULT_DB_ALIAS
|
||||
from django.apps import apps as global_apps
|
||||
from django.apps import AppConfig
|
||||
|
||||
from notifications.backends import BACKEND
|
||||
from users.models import User
|
||||
from common.utils.connection import RedisPubSub
|
||||
from common.utils import get_logger
|
||||
from common.decorator import on_transaction_commit
|
||||
from .models import SiteMessage, SystemMsgSubscription
|
||||
from .models import SiteMessage, SystemMsgSubscription, UserMsgSubscription
|
||||
from .notifications import SystemMessage
|
||||
|
||||
|
||||
@@ -82,3 +82,13 @@ def create_system_messages(app_config: AppConfig, **kwargs):
|
||||
logger.info(f'Create SystemMsgSubscription: package={app_config.module.__package__} type={message_type}')
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def on_user_post_save(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
receive_backends = []
|
||||
for backend in BACKEND:
|
||||
if backend.get_account(instance):
|
||||
receive_backends.append(backend)
|
||||
UserMsgSubscription.objects.create(user=instance, receive_backends=receive_backends)
|
||||
|
||||
@@ -2,9 +2,12 @@ from django.db.models import F
|
||||
from django.db import transaction
|
||||
|
||||
from common.utils.timezone import now
|
||||
from common.utils import get_logger
|
||||
from users.models import User
|
||||
from .models import SiteMessage as SiteMessageModel, SiteMessageUsers
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class SiteMessageUtil:
|
||||
|
||||
@@ -14,6 +17,11 @@ class SiteMessageUtil:
|
||||
if not any((user_ids, group_ids, is_broadcast)):
|
||||
raise ValueError('No recipient is specified')
|
||||
|
||||
logger.info(f'Site message send: '
|
||||
f'user_ids={user_ids} '
|
||||
f'group_ids={group_ids} '
|
||||
f'subject={subject} '
|
||||
f'message={message}')
|
||||
with transaction.atomic():
|
||||
site_msg = SiteMessageModel.objects.create(
|
||||
subject=subject, message=message,
|
||||
|
||||
@@ -8,6 +8,7 @@ app_name = 'notifications'
|
||||
|
||||
router = BulkRouter()
|
||||
router.register('system-msg-subscription', api.SystemMsgSubscriptionViewSet, 'system-msg-subscription')
|
||||
router.register('user-msg-subscription', api.UserMsgSubscriptionViewSet, 'user-msg-subscription')
|
||||
router.register('site-message', api.SiteMessageViewSet, 'site-message')
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
Reference in New Issue
Block a user