- 通知渠道增加 sign_secret 字段存储加签密钥 - 发送钉钉消息时自动计算签名 - 前端增加加签密钥输入框(仅钉钉机器人显示)
This commit is contained in:
@@ -13,6 +13,7 @@ class TaskNotifyChannel(Base):
|
||||
channel_name = Column(String(100), nullable=False)
|
||||
channel_type = Column(Enum('dingtalk_bot', 'wecom_bot'), nullable=False)
|
||||
webhook_url = Column(String(500), nullable=False)
|
||||
sign_secret = Column(String(200)) # 钉钉加签密钥
|
||||
description = Column(String(255))
|
||||
is_enabled = Column(Boolean, default=True)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ class ChannelCreate(BaseModel):
|
||||
channel_name: str
|
||||
channel_type: str # dingtalk_bot, wecom_bot
|
||||
webhook_url: str
|
||||
sign_secret: Optional[str] = None # 钉钉加签密钥
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
@@ -26,6 +27,7 @@ class ChannelUpdate(BaseModel):
|
||||
channel_name: Optional[str] = None
|
||||
channel_type: Optional[str] = None
|
||||
webhook_url: Optional[str] = None
|
||||
sign_secret: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
is_enabled: Optional[bool] = None
|
||||
|
||||
@@ -77,6 +79,7 @@ async def create_channel(data: ChannelCreate, db: Session = Depends(get_db)):
|
||||
channel_name=data.channel_name,
|
||||
channel_type=data.channel_type,
|
||||
webhook_url=data.webhook_url,
|
||||
sign_secret=data.sign_secret,
|
||||
description=data.description,
|
||||
is_enabled=True
|
||||
)
|
||||
@@ -101,6 +104,8 @@ async def update_channel(channel_id: int, data: ChannelUpdate, db: Session = Dep
|
||||
channel.channel_type = data.channel_type
|
||||
if data.webhook_url is not None:
|
||||
channel.webhook_url = data.webhook_url
|
||||
if data.sign_secret is not None:
|
||||
channel.sign_secret = data.sign_secret
|
||||
if data.description is not None:
|
||||
channel.description = data.description
|
||||
if data.is_enabled is not None:
|
||||
@@ -126,6 +131,11 @@ async def delete_channel(channel_id: int, db: Session = Depends(get_db)):
|
||||
async def test_channel(channel_id: int, db: Session = Depends(get_db)):
|
||||
"""测试通知渠道"""
|
||||
import httpx
|
||||
import time
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import urllib.parse
|
||||
|
||||
channel = db.query(TaskNotifyChannel).filter(TaskNotifyChannel.id == channel_id).first()
|
||||
if not channel:
|
||||
@@ -134,7 +144,26 @@ async def test_channel(channel_id: int, db: Session = Depends(get_db)):
|
||||
test_content = f"**测试消息**\n\n渠道名称: {channel.channel_name}\n发送时间: 测试中..."
|
||||
|
||||
try:
|
||||
url = channel.webhook_url
|
||||
|
||||
if channel.channel_type == 'dingtalk_bot':
|
||||
# 钉钉加签
|
||||
if channel.sign_secret:
|
||||
timestamp = str(round(time.time() * 1000))
|
||||
string_to_sign = f'{timestamp}\n{channel.sign_secret}'
|
||||
hmac_code = hmac.new(
|
||||
channel.sign_secret.encode('utf-8'),
|
||||
string_to_sign.encode('utf-8'),
|
||||
digestmod=hashlib.sha256
|
||||
).digest()
|
||||
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
|
||||
|
||||
# 拼接签名参数
|
||||
if '?' in url:
|
||||
url = f"{url}×tamp={timestamp}&sign={sign}"
|
||||
else:
|
||||
url = f"{url}?timestamp={timestamp}&sign={sign}"
|
||||
|
||||
payload = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
@@ -151,7 +180,7 @@ async def test_channel(channel_id: int, db: Session = Depends(get_db)):
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
response = await client.post(channel.webhook_url, json=payload)
|
||||
response = await client.post(url, json=payload)
|
||||
result = response.json()
|
||||
|
||||
# 钉钉返回 errcode=0,企微返回 errcode=0
|
||||
@@ -174,6 +203,7 @@ def format_channel(channel: TaskNotifyChannel) -> dict:
|
||||
"channel_name": channel.channel_name,
|
||||
"channel_type": channel.channel_type,
|
||||
"webhook_url": channel.webhook_url,
|
||||
"sign_secret": channel.sign_secret,
|
||||
"description": channel.description,
|
||||
"is_enabled": channel.is_enabled,
|
||||
"created_at": channel.created_at,
|
||||
|
||||
@@ -282,7 +282,31 @@ class SchedulerService:
|
||||
|
||||
async def _send_to_channel(self, channel: TaskNotifyChannel, content: str, title: str):
|
||||
"""发送消息到通知渠道"""
|
||||
import time
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import urllib.parse
|
||||
|
||||
url = channel.webhook_url
|
||||
|
||||
if channel.channel_type == 'dingtalk_bot':
|
||||
# 钉钉加签
|
||||
if channel.sign_secret:
|
||||
timestamp = str(round(time.time() * 1000))
|
||||
string_to_sign = f'{timestamp}\n{channel.sign_secret}'
|
||||
hmac_code = hmac.new(
|
||||
channel.sign_secret.encode('utf-8'),
|
||||
string_to_sign.encode('utf-8'),
|
||||
digestmod=hashlib.sha256
|
||||
).digest()
|
||||
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
|
||||
|
||||
if '?' in url:
|
||||
url = f"{url}×tamp={timestamp}&sign={sign}"
|
||||
else:
|
||||
url = f"{url}?timestamp={timestamp}&sign={sign}"
|
||||
|
||||
payload = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
@@ -299,7 +323,7 @@ class SchedulerService:
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
response = await client.post(channel.webhook_url, json=payload)
|
||||
response = await client.post(url, json=payload)
|
||||
result = response.json()
|
||||
if result.get('errcode') != 0:
|
||||
print(f"通知发送失败: {result}")
|
||||
|
||||
@@ -25,6 +25,7 @@ const form = reactive({
|
||||
channel_name: '',
|
||||
channel_type: 'dingtalk_bot',
|
||||
webhook_url: '',
|
||||
sign_secret: '',
|
||||
description: ''
|
||||
})
|
||||
|
||||
@@ -78,6 +79,7 @@ function handleCreate() {
|
||||
channel_name: '',
|
||||
channel_type: 'dingtalk_bot',
|
||||
webhook_url: '',
|
||||
sign_secret: '',
|
||||
description: ''
|
||||
})
|
||||
dialogVisible.value = true
|
||||
@@ -91,6 +93,7 @@ function handleEdit(row) {
|
||||
channel_name: row.channel_name,
|
||||
channel_type: row.channel_type,
|
||||
webhook_url: row.webhook_url,
|
||||
sign_secret: row.sign_secret || '',
|
||||
description: row.description || ''
|
||||
})
|
||||
dialogVisible.value = true
|
||||
@@ -258,6 +261,12 @@ onMounted(() => {
|
||||
</template>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.channel_type === 'dingtalk_bot'" label="加签密钥">
|
||||
<el-input v-model="form.sign_secret" placeholder="SEC开头的密钥(可选)" />
|
||||
<div class="form-tip">
|
||||
如果创建机器人时选择了「加签」安全设置,请填写密钥(以 SEC 开头)
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="form.description" type="textarea" :rows="2" placeholder="渠道描述(可选)" />
|
||||
</el-form-item>
|
||||
|
||||
Reference in New Issue
Block a user