Files
LiteOps/backend/apps/views/notification.py

407 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import uuid
import hashlib
import hmac
import base64
import time
import logging
import requests
from urllib.parse import quote_plus
from django.http import JsonResponse
from django.views import View
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.db import transaction
from ..models import NotificationRobot, User
from ..utils.auth import jwt_auth_required
logger = logging.getLogger('apps')
def generate_id():
"""生成唯一ID"""
return hashlib.sha256(str(uuid.uuid4()).encode()).hexdigest()[:32]
@method_decorator(csrf_exempt, name='dispatch')
class NotificationRobotView(View):
@method_decorator(jwt_auth_required)
def get(self, request, robot_id=None):
"""获取机器人列表或单个机器人详情"""
try:
if robot_id:
try:
robot = NotificationRobot.objects.select_related('creator').get(robot_id=robot_id)
return JsonResponse({
'code': 200,
'message': '获取机器人详情成功',
'data': {
'robot_id': robot.robot_id,
'type': robot.type,
'name': robot.name,
'webhook': robot.webhook,
'security_type': robot.security_type,
'secret': robot.secret,
'keywords': robot.keywords,
'ip_list': robot.ip_list,
'remark': robot.remark,
'creator': {
'user_id': robot.creator.user_id,
'name': robot.creator.name
} if robot.creator else None,
'create_time': robot.create_time.strftime('%Y-%m-%d %H:%M:%S'),
'update_time': robot.update_time.strftime('%Y-%m-%d %H:%M:%S')
}
})
except NotificationRobot.DoesNotExist:
return JsonResponse({
'code': 404,
'message': '机器人不存在'
})
# 获取所有机器人列表
robots = NotificationRobot.objects.select_related('creator').all()
robot_list = []
for robot in robots:
robot_list.append({
'robot_id': robot.robot_id,
'type': robot.type,
'name': robot.name,
'webhook': robot.webhook,
'security_type': robot.security_type,
'secret': robot.secret,
'keywords': robot.keywords,
'ip_list': robot.ip_list,
'remark': robot.remark,
'creator': {
'user_id': robot.creator.user_id,
'name': robot.creator.name
} if robot.creator else None,
'create_time': robot.create_time.strftime('%Y-%m-%d %H:%M:%S')
})
return JsonResponse({
'code': 200,
'message': '获取机器人列表成功',
'data': robot_list
})
except Exception as e:
logger.error(f'获取机器人失败: {str(e)}', exc_info=True)
return JsonResponse({
'code': 500,
'message': f'服务器错误: {str(e)}'
})
@method_decorator(jwt_auth_required)
def post(self, request):
"""创建机器人"""
try:
data = json.loads(request.body)
robot_type = data.get('type')
name = data.get('name')
webhook = data.get('webhook')
security_type = data.get('security_type', 'none')
secret = data.get('secret')
keywords = data.get('keywords', [])
ip_list = data.get('ip_list', [])
remark = data.get('remark')
# 验证必要字段
if not all([robot_type, name, webhook]):
return JsonResponse({
'code': 400,
'message': '机器人类型、名称和Webhook地址不能为空'
})
# 验证机器人类型
if robot_type not in ['dingtalk', 'wecom', 'feishu']:
return JsonResponse({
'code': 400,
'message': '不支持的机器人类型'
})
# 验证安全设置
if security_type == 'secret' and not secret:
return JsonResponse({
'code': 400,
'message': '使用加签密钥时,密钥不能为空'
})
elif security_type == 'keyword' and not keywords:
return JsonResponse({
'code': 400,
'message': '使用自定义关键词时,关键词不能为空'
})
elif security_type == 'ip' and not ip_list:
return JsonResponse({
'code': 400,
'message': '使用IP白名单时IP列表不能为空'
})
# 创建机器人
creator = User.objects.get(user_id=request.user_id)
robot = NotificationRobot.objects.create(
robot_id=generate_id(),
type=robot_type,
name=name,
webhook=webhook,
security_type=security_type,
secret=secret,
keywords=keywords,
ip_list=ip_list,
remark=remark,
creator=creator
)
return JsonResponse({
'code': 200,
'message': '创建机器人成功',
'data': {
'robot_id': robot.robot_id
}
})
except Exception as e:
logger.error(f'创建机器人失败: {str(e)}', exc_info=True)
return JsonResponse({
'code': 500,
'message': f'服务器错误: {str(e)}'
})
@method_decorator(jwt_auth_required)
def put(self, request):
"""更新机器人"""
try:
data = json.loads(request.body)
robot_id = data.get('robot_id')
name = data.get('name')
webhook = data.get('webhook')
security_type = data.get('security_type')
secret = data.get('secret')
keywords = data.get('keywords')
ip_list = data.get('ip_list')
remark = data.get('remark')
if not robot_id:
return JsonResponse({
'code': 400,
'message': '机器人ID不能为空'
})
try:
robot = NotificationRobot.objects.get(robot_id=robot_id)
except NotificationRobot.DoesNotExist:
return JsonResponse({
'code': 404,
'message': '机器人不存在'
})
# 验证必要字段
if name:
robot.name = name
if webhook:
robot.webhook = webhook
if security_type:
robot.security_type = security_type
if secret is not None:
robot.secret = secret
if keywords is not None:
robot.keywords = keywords
if ip_list is not None:
robot.ip_list = ip_list
if remark is not None:
robot.remark = remark
# 验证安全设置
if robot.security_type == 'secret' and not robot.secret:
return JsonResponse({
'code': 400,
'message': '使用加签密钥时,密钥不能为空'
})
elif robot.security_type == 'keyword' and not robot.keywords:
return JsonResponse({
'code': 400,
'message': '使用自定义关键词时,关键词不能为空'
})
elif robot.security_type == 'ip' and not robot.ip_list:
return JsonResponse({
'code': 400,
'message': '使用IP白名单时IP列表不能为空'
})
robot.save()
return JsonResponse({
'code': 200,
'message': '更新机器人成功'
})
except Exception as e:
logger.error(f'更新机器人失败: {str(e)}', exc_info=True)
return JsonResponse({
'code': 500,
'message': f'服务器错误: {str(e)}'
})
@method_decorator(jwt_auth_required)
def delete(self, request):
"""删除机器人"""
try:
data = json.loads(request.body)
robot_id = data.get('robot_id')
if not robot_id:
return JsonResponse({
'code': 400,
'message': '机器人ID不能为空'
})
try:
robot = NotificationRobot.objects.get(robot_id=robot_id)
robot.delete()
return JsonResponse({
'code': 200,
'message': '删除机器人成功'
})
except NotificationRobot.DoesNotExist:
return JsonResponse({
'code': 404,
'message': '机器人不存在'
})
except Exception as e:
logger.error(f'删除机器人失败: {str(e)}', exc_info=True)
return JsonResponse({
'code': 500,
'message': f'服务器错误: {str(e)}'
})
@method_decorator(csrf_exempt, name='dispatch')
class NotificationTestView(View):
def _sign_dingtalk(self, secret, timestamp):
"""钉钉机器人签名"""
string_to_sign = f'{timestamp}\n{secret}'
hmac_code = hmac.new(
secret.encode('utf-8'),
string_to_sign.encode('utf-8'),
digestmod=hashlib.sha256
).digest()
return base64.b64encode(hmac_code).decode('utf-8')
def _sign_feishu(self, secret, timestamp):
"""飞书机器人签名"""
string_to_sign = f'{timestamp}\n{secret}'
hmac_code = hmac.new(
secret.encode('utf-8'),
string_to_sign.encode('utf-8'),
digestmod=hashlib.sha256
).digest()
return base64.b64encode(hmac_code).decode('utf-8')
@method_decorator(jwt_auth_required)
def post(self, request, *args, **kwargs):
"""测试机器人"""
try:
data = json.loads(request.body)
robot_id = data.get('robot_id')
if not robot_id:
return JsonResponse({
'code': 400,
'message': '机器人ID不能为空'
})
try:
robot = NotificationRobot.objects.get(robot_id=robot_id)
except NotificationRobot.DoesNotExist:
return JsonResponse({
'code': 404,
'message': '机器人不存在'
})
# 准备测试消息
timestamp = str(int(time.time() * 1000))
test_message = "这是一条测试消息,如果你收到了这条消息,说明机器人配置正确。"
# 使用自定义关键词安全设置,需要在消息中包含关键词
if robot.security_type == 'keyword' and robot.keywords:
# 测试消息中关键词
keyword = robot.keywords[0]
test_message = f"{keyword}{test_message}"
# 根据不同类型的机器人发送测试消息
try:
if robot.type == 'dingtalk':
# 钉钉机器人
webhook = robot.webhook
# 如果使用加签方式
if robot.security_type == 'secret' and robot.secret:
sign = self._sign_dingtalk(robot.secret, timestamp)
webhook = f"{webhook}&timestamp={timestamp}&sign={quote_plus(sign)}"
# 构建消息内容
message_data = {
"msgtype": "text",
"text": {
"content": test_message
}
}
response = requests.post(webhook, json=message_data)
elif robot.type == 'wecom':
# 企业微信机器人
response = requests.post(robot.webhook, json={
"msgtype": "text",
"text": {
"content": test_message
}
})
elif robot.type == 'feishu':
# 飞书机器人
headers = {}
if robot.security_type == 'secret' and robot.secret:
sign = self._sign_feishu(robot.secret, timestamp)
headers.update({
"X-Timestamp": timestamp,
"X-Sign": sign
})
response = requests.post(robot.webhook, json={
"msg_type": "text",
"content": {
"text": test_message
}
}, headers=headers)
if response.status_code == 200:
resp_json = response.json()
if resp_json.get('errcode') == 0 or resp_json.get('StatusCode') == 0 or resp_json.get('code') == 0:
return JsonResponse({
'code': 200,
'message': '测试消息发送成功'
})
else:
return JsonResponse({
'code': 400,
'message': f'测试消息发送失败: {response.text}'
})
else:
return JsonResponse({
'code': 400,
'message': f'测试消息发送失败: {response.text}'
})
except Exception as e:
logger.error(f'发送测试消息失败: {str(e)}', exc_info=True)
return JsonResponse({
'code': 500,
'message': f'发送测试消息失败: {str(e)}'
})
except Exception as e:
logger.error(f'测试机器人失败: {str(e)}', exc_info=True)
return JsonResponse({
'code': 500,
'message': f'服务器错误: {str(e)}'
})