"""通知渠道API路由""" from typing import Optional, List from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from sqlalchemy.orm import Session from sqlalchemy import desc from ..database import get_db from ..models.notification_channel import TaskNotifyChannel router = APIRouter(prefix="/api/notification-channels", tags=["notification-channels"]) # ==================== Schemas ==================== class ChannelCreate(BaseModel): tenant_id: str channel_name: str channel_type: str # dingtalk_bot, wecom_bot webhook_url: str sign_secret: Optional[str] = None # 钉钉加签密钥 description: Optional[str] = None 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 # ==================== CRUD ==================== @router.get("") async def list_channels( tenant_id: Optional[str] = None, channel_type: Optional[str] = None, is_enabled: Optional[bool] = None, page: int = Query(1, ge=1), size: int = Query(50, ge=1, le=100), db: Session = Depends(get_db) ): """获取通知渠道列表""" query = db.query(TaskNotifyChannel) if tenant_id: query = query.filter(TaskNotifyChannel.tenant_id == tenant_id) if channel_type: query = query.filter(TaskNotifyChannel.channel_type == channel_type) if is_enabled is not None: query = query.filter(TaskNotifyChannel.is_enabled == is_enabled) total = query.count() items = query.order_by(desc(TaskNotifyChannel.created_at)).offset((page - 1) * size).limit(size).all() return { "total": total, "items": [format_channel(c) for c in items] } @router.get("/{channel_id}") async def get_channel(channel_id: int, db: Session = Depends(get_db)): """获取渠道详情""" channel = db.query(TaskNotifyChannel).filter(TaskNotifyChannel.id == channel_id).first() if not channel: raise HTTPException(status_code=404, detail="渠道不存在") return format_channel(channel) @router.post("") async def create_channel(data: ChannelCreate, db: Session = Depends(get_db)): """创建通知渠道""" channel = TaskNotifyChannel( tenant_id=data.tenant_id, 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 ) db.add(channel) db.commit() db.refresh(channel) return {"success": True, "id": channel.id} @router.put("/{channel_id}") async def update_channel(channel_id: int, data: ChannelUpdate, db: Session = Depends(get_db)): """更新通知渠道""" channel = db.query(TaskNotifyChannel).filter(TaskNotifyChannel.id == channel_id).first() if not channel: raise HTTPException(status_code=404, detail="渠道不存在") if data.channel_name is not None: channel.channel_name = data.channel_name if data.channel_type is not None: 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: channel.is_enabled = data.is_enabled db.commit() return {"success": True} @router.delete("/{channel_id}") async def delete_channel(channel_id: int, db: Session = Depends(get_db)): """删除通知渠道""" channel = db.query(TaskNotifyChannel).filter(TaskNotifyChannel.id == channel_id).first() if not channel: raise HTTPException(status_code=404, detail="渠道不存在") db.delete(channel) db.commit() return {"success": True} @router.post("/{channel_id}/test") 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: raise HTTPException(status_code=404, detail="渠道不存在") 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": { "title": "渠道测试", "text": test_content } } else: # wecom_bot payload = { "msgtype": "markdown", "markdown": { "content": test_content } } async with httpx.AsyncClient(timeout=10) as client: response = await client.post(url, json=payload) result = response.json() # 钉钉返回 errcode=0,企微返回 errcode=0 if result.get('errcode') == 0: return {"success": True, "message": "发送成功"} else: return {"success": False, "message": f"发送失败: {result}"} except Exception as e: return {"success": False, "message": f"发送失败: {str(e)}"} # ==================== Helpers ==================== def format_channel(channel: TaskNotifyChannel) -> dict: """格式化渠道数据""" return { "id": channel.id, "tenant_id": channel.tenant_id, "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, "updated_at": channel.updated_at }