Skip to content

Commit 648a188

Browse files
author
yyhuni
committed
增加企业微信
1 parent 2508268 commit 648a188

File tree

8 files changed

+252
-95
lines changed

8 files changed

+252
-95
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated manually for WeCom notification support
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('scan', '0002_add_cached_screenshots_count'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='notificationsettings',
15+
name='wecom_enabled',
16+
field=models.BooleanField(default=False, help_text='是否启用企业微信通知'),
17+
),
18+
migrations.AddField(
19+
model_name='notificationsettings',
20+
name='wecom_webhook_url',
21+
field=models.URLField(blank=True, default='', help_text='企业微信机器人 Webhook URL'),
22+
),
23+
]
Lines changed: 38 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,49 @@
11
"""通知系统数据模型"""
22

3+
import logging
4+
from datetime import timedelta
5+
36
from django.db import models
7+
from django.utils import timezone
8+
9+
from .types import NotificationCategory, NotificationLevel
410

5-
from .types import NotificationLevel, NotificationCategory
11+
logger = logging.getLogger(__name__)
612

713

814
class NotificationSettings(models.Model):
915
"""
1016
通知设置(单例模型)
1117
存储 Discord webhook 配置和各分类的通知开关
1218
"""
13-
19+
1420
# Discord 配置
1521
discord_enabled = models.BooleanField(default=False, help_text='是否启用 Discord 通知')
1622
discord_webhook_url = models.URLField(blank=True, default='', help_text='Discord Webhook URL')
17-
23+
24+
# 企业微信配置
25+
wecom_enabled = models.BooleanField(default=False, help_text='是否启用企业微信通知')
26+
wecom_webhook_url = models.URLField(blank=True, default='', help_text='企业微信机器人 Webhook URL')
27+
1828
# 分类开关(使用 JSONField 存储)
1929
categories = models.JSONField(
2030
default=dict,
2131
help_text='各分类通知开关,如 {"scan": true, "vulnerability": true, "asset": true, "system": false}'
2232
)
23-
33+
2434
# 时间信息
2535
created_at = models.DateTimeField(auto_now_add=True)
2636
updated_at = models.DateTimeField(auto_now=True)
27-
37+
2838
class Meta:
2939
db_table = 'notification_settings'
3040
verbose_name = '通知设置'
3141
verbose_name_plural = '通知设置'
32-
42+
3343
def save(self, *args, **kwargs):
34-
# 单例模式:强制只有一条记录
35-
self.pk = 1
44+
self.pk = 1 # 单例模式
3645
super().save(*args, **kwargs)
37-
46+
3847
@classmethod
3948
def get_instance(cls) -> 'NotificationSettings':
4049
"""获取或创建单例实例"""
@@ -52,44 +61,41 @@ def get_instance(cls) -> 'NotificationSettings':
5261
}
5362
)
5463
return obj
55-
64+
5665
def is_category_enabled(self, category: str) -> bool:
5766
"""检查指定分类是否启用通知"""
5867
return self.categories.get(category, False)
5968

6069

6170
class Notification(models.Model):
6271
"""通知模型"""
63-
72+
6473
id = models.AutoField(primary_key=True)
65-
66-
# 通知分类
74+
6775
category = models.CharField(
6876
max_length=20,
6977
choices=NotificationCategory.choices,
7078
default=NotificationCategory.SYSTEM,
7179
db_index=True,
7280
help_text='通知分类'
7381
)
74-
75-
# 通知级别
82+
7683
level = models.CharField(
7784
max_length=20,
7885
choices=NotificationLevel.choices,
7986
default=NotificationLevel.LOW,
8087
db_index=True,
8188
help_text='通知级别'
8289
)
83-
90+
8491
title = models.CharField(max_length=200, help_text='通知标题')
8592
message = models.CharField(max_length=2000, help_text='通知内容')
86-
87-
# 时间信息
93+
8894
created_at = models.DateTimeField(auto_now_add=True, help_text='创建时间')
89-
95+
9096
is_read = models.BooleanField(default=False, help_text='是否已读')
9197
read_at = models.DateTimeField(null=True, blank=True, help_text='阅读时间')
92-
98+
9399
class Meta:
94100
db_table = 'notification'
95101
verbose_name = '通知'
@@ -101,44 +107,26 @@ class Meta:
101107
models.Index(fields=['level', '-created_at']),
102108
models.Index(fields=['is_read', '-created_at']),
103109
]
104-
110+
105111
def __str__(self):
106112
return f"{self.get_level_display()} - {self.title}"
107-
113+
108114
@classmethod
109-
def cleanup_old_notifications(cls):
110-
"""
111-
清理超过15天的旧通知(硬编码)
112-
113-
Returns:
114-
int: 删除的通知数量
115-
"""
116-
from datetime import timedelta
117-
from django.utils import timezone
118-
119-
# 硬编码:只保留最近15天的通知
115+
def cleanup_old_notifications(cls) -> int:
116+
"""清理超过15天的旧通知"""
120117
cutoff_date = timezone.now() - timedelta(days=15)
121-
delete_result = cls.objects.filter(created_at__lt=cutoff_date).delete()
122-
123-
return delete_result[0] if delete_result[0] else 0
124-
118+
deleted_count, _ = cls.objects.filter(created_at__lt=cutoff_date).delete()
119+
return deleted_count or 0
120+
125121
def save(self, *args, **kwargs):
126-
"""
127-
重写save方法,在创建新通知时自动清理旧通知
128-
"""
122+
"""重写save方法,在创建新通知时自动清理旧通知"""
129123
is_new = self.pk is None
130124
super().save(*args, **kwargs)
131-
132-
# 只在创建新通知时执行清理(自动清理超过15天的通知)
125+
133126
if is_new:
134127
try:
135128
deleted_count = self.__class__.cleanup_old_notifications()
136129
if deleted_count > 0:
137-
import logging
138-
logger = logging.getLogger(__name__)
139-
logger.info(f"自动清理了 {deleted_count} 条超过15天的旧通知")
140-
except Exception as e:
141-
# 清理失败不应影响通知创建
142-
import logging
143-
logger = logging.getLogger(__name__)
144-
logger.warning(f"通知自动清理失败: {e}")
130+
logger.info("自动清理了 %d 条超过15天的旧通知", deleted_count)
131+
except Exception:
132+
logger.warning("通知自动清理失败", exc_info=True)

backend/apps/scan/notifications/repositories.py

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,70 @@
1+
"""通知系统仓储层模块"""
2+
13
import logging
2-
from typing import TypedDict
4+
from dataclasses import dataclass
5+
from typing import Optional
6+
7+
from django.db.models import QuerySet
38
from django.utils import timezone
49

510
from apps.common.decorators import auto_ensure_db_connection
6-
from .models import Notification, NotificationSettings
711

12+
from .models import Notification, NotificationSettings
813

914
logger = logging.getLogger(__name__)
1015

1116

12-
class NotificationSettingsData(TypedDict):
13-
"""通知设置数据结构"""
17+
@dataclass
18+
class NotificationSettingsData:
19+
"""通知设置更新数据"""
20+
1421
discord_enabled: bool
1522
discord_webhook_url: str
1623
categories: dict[str, bool]
24+
wecom_enabled: bool = False
25+
wecom_webhook_url: str = ''
1726

1827

1928
@auto_ensure_db_connection
2029
class NotificationSettingsRepository:
2130
"""通知设置仓储层"""
22-
31+
2332
def get_settings(self) -> NotificationSettings:
2433
"""获取通知设置单例"""
2534
return NotificationSettings.get_instance()
26-
27-
def update_settings(
28-
self,
29-
discord_enabled: bool,
30-
discord_webhook_url: str,
31-
categories: dict[str, bool]
32-
) -> NotificationSettings:
35+
36+
def update_settings(self, data: NotificationSettingsData) -> NotificationSettings:
3337
"""更新通知设置"""
3438
settings = NotificationSettings.get_instance()
35-
settings.discord_enabled = discord_enabled
36-
settings.discord_webhook_url = discord_webhook_url
37-
settings.categories = categories
39+
settings.discord_enabled = data.discord_enabled
40+
settings.discord_webhook_url = data.discord_webhook_url
41+
settings.wecom_enabled = data.wecom_enabled
42+
settings.wecom_webhook_url = data.wecom_webhook_url
43+
settings.categories = data.categories
3844
settings.save()
3945
return settings
40-
46+
4147
def is_category_enabled(self, category: str) -> bool:
4248
"""检查指定分类是否启用"""
43-
settings = self.get_settings()
44-
return settings.is_category_enabled(category)
49+
return self.get_settings().is_category_enabled(category)
4550

4651

4752
@auto_ensure_db_connection
4853
class DjangoNotificationRepository:
49-
def get_filtered(self, level: str | None = None, unread: bool | None = None):
54+
"""通知数据仓储层"""
55+
56+
def get_filtered(
57+
self,
58+
level: Optional[str] = None,
59+
unread: Optional[bool] = None
60+
) -> QuerySet[Notification]:
61+
"""
62+
获取过滤后的通知列表
63+
64+
Args:
65+
level: 通知级别过滤
66+
unread: 已读状态过滤 (True=未读, False=已读, None=全部)
67+
"""
5068
queryset = Notification.objects.all()
5169

5270
if level:
@@ -60,16 +78,24 @@ def get_filtered(self, level: str | None = None, unread: bool | None = None):
6078
return queryset.order_by("-created_at")
6179

6280
def get_unread_count(self) -> int:
81+
"""获取未读通知数量"""
6382
return Notification.objects.filter(is_read=False).count()
6483

6584
def mark_all_as_read(self) -> int:
66-
updated = Notification.objects.filter(is_read=False).update(
85+
"""标记所有通知为已读,返回更新数量"""
86+
return Notification.objects.filter(is_read=False).update(
6787
is_read=True,
6888
read_at=timezone.now(),
6989
)
70-
return updated
7190

72-
def create(self, title: str, message: str, level: str, category: str = 'system') -> Notification:
91+
def create(
92+
self,
93+
title: str,
94+
message: str,
95+
level: str,
96+
category: str = 'system'
97+
) -> Notification:
98+
"""创建新通知"""
7399
return Notification.objects.create(
74100
category=category,
75101
level=level,

0 commit comments

Comments
 (0)