Files
opsxsql/backend/app/api/v1/endpoints/notification.py
T
2026-05-24 14:08:45 +08:00

154 lines
4.9 KiB
Python

import json
import copy
from typing import List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.security import require_role
from app.models.user import User
from app.models.notification import NotificationChannel
from app.schemas.notification import (
NotificationChannelCreate,
NotificationChannelUpdate,
NotificationChannelResponse,
)
router = APIRouter(prefix="/notifications", tags=["消息推送配置"])
def _mask_config(config: dict) -> dict:
"""Mask sensitive fields in config for response."""
masked = copy.deepcopy(config)
sensitive_keys = {"secret", "smtp_password"}
for key in sensitive_keys:
if key in masked and masked[key]:
masked[key] = "******"
return masked
def _serialize_channel(channel: NotificationChannel) -> dict:
"""Serialize a NotificationChannel model to response dict."""
config = json.loads(channel.config) if channel.config else {}
return {
"id": channel.id,
"name": channel.name,
"channel_type": channel.channel_type,
"config": _mask_config(config),
"is_enabled": channel.is_enabled,
"created_at": channel.created_at,
"updated_at": channel.updated_at,
}
@router.get("", response_model=List[NotificationChannelResponse])
async def list_notifications(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""管理员获取通知渠道列表"""
result = await db.execute(select(NotificationChannel).order_by(NotificationChannel.id))
channels = result.scalars().all()
return [_serialize_channel(c) for c in channels]
@router.post("", response_model=NotificationChannelResponse, status_code=201)
async def create_notification(
data: NotificationChannelCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""管理员创建通知渠道"""
channel = NotificationChannel(
name=data.name,
channel_type=data.channel_type,
config=json.dumps(data.config, ensure_ascii=False),
is_enabled=data.is_enabled,
)
db.add(channel)
await db.commit()
await db.refresh(channel)
return _serialize_channel(channel)
@router.put("/{channel_id}", response_model=NotificationChannelResponse)
async def update_notification(
channel_id: int,
data: NotificationChannelUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""管理员更新通知渠道"""
result = await db.execute(
select(NotificationChannel).where(NotificationChannel.id == channel_id)
)
channel = result.scalar_one_or_none()
if not channel:
raise HTTPException(status_code=404, detail="通知渠道不存在")
update_data = data.model_dump(exclude_unset=True)
if "config" in update_data:
update_data["config"] = json.dumps(update_data["config"], ensure_ascii=False)
for key, value in update_data.items():
setattr(channel, key, value)
await db.commit()
await db.refresh(channel)
return _serialize_channel(channel)
@router.delete("/{channel_id}")
async def delete_notification(
channel_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""管理员删除通知渠道"""
result = await db.execute(
select(NotificationChannel).where(NotificationChannel.id == channel_id)
)
channel = result.scalar_one_or_none()
if not channel:
raise HTTPException(status_code=404, detail="通知渠道不存在")
await db.delete(channel)
await db.commit()
return {"message": "通知渠道已删除"}
@router.post("/{channel_id}/test")
async def test_notification(
channel_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""测试发送通知 — 实际发送测试消息到配置的渠道。"""
from app.services.notification_sender import NotificationSender
result = await db.execute(
select(NotificationChannel).where(NotificationChannel.id == channel_id)
)
channel = result.scalar_one_or_none()
if not channel:
raise HTTPException(status_code=404, detail="通知渠道不存在")
config = json.loads(channel.config) if channel.config else {}
sender = NotificationSender()
test_data = {
"title": "测试通知",
"applicant": current_user.display_name or current_user.username,
"db_type": "mysql",
"target_database": "test_db",
"sql_preview": "SELECT 1",
"priority": "normal",
"approver": "系统",
"ticket_id": "TEST",
"result": "成功",
"duration": "10",
"affected_rows": "0",
}
result = await sender.send(channel.channel_type, config, "ticket_created", test_data)
return result