"""Audit rule management endpoints.""" from typing import Optional, List from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import select, func 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.audit_rule import AuditRule from app.schemas.audit_rule import AuditRuleCreate, AuditRuleUpdate, AuditRuleResponse from app.services.audit_rule_seeds import SEED_RULES_MAP from app.services.audit_rule_seeds_v2 import seed_all_rules router = APIRouter(prefix="/audit-rules", tags=["审核规则"]) @router.get("", response_model=List[AuditRuleResponse]) async def list_audit_rules( rule_type: Optional[str] = None, severity: Optional[str] = None, db_type: Optional[str] = None, is_enabled: Optional[bool] = None, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin", "dba")), ): stmt = select(AuditRule).order_by(AuditRule.id) if rule_type: stmt = stmt.where(AuditRule.rule_type == rule_type) if severity: stmt = stmt.where(AuditRule.severity == severity) if db_type: # 支持类型别名: postgresql -> postgres, mongo -> mongodb db_type_aliases = {"postgresql": "postgres", "pg": "postgres", "mongo": "mongodb"} resolved = db_type_aliases.get(db_type.lower(), db_type.lower()) stmt = stmt.where((AuditRule.db_type == resolved) | (AuditRule.db_type == db_type) | (AuditRule.db_type == "all")) if is_enabled is not None: stmt = stmt.where(AuditRule.is_enabled == is_enabled) result = await db.execute(stmt) return result.scalars().all() @router.post("", response_model=AuditRuleResponse, status_code=201) async def create_audit_rule( data: AuditRuleCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): exists = await db.execute(select(AuditRule).where(AuditRule.name == data.name)) if exists.scalar_one_or_none(): raise HTTPException(status_code=400, detail="规则名称已存在") rule = AuditRule(**data.model_dump()) db.add(rule) await db.commit() await db.refresh(rule) return rule @router.get("/stats/summary") async def rule_stats( db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin", "dba")), ): total_stmt = select(func.count(AuditRule.id)) total = (await db.execute(total_stmt)).scalar() enabled_stmt = select(func.count(AuditRule.id)).where(AuditRule.is_enabled == True) enabled = (await db.execute(enabled_stmt)).scalar() type_stmt = select(AuditRule.rule_type, func.count(AuditRule.id)).group_by(AuditRule.rule_type) type_result = (await db.execute(type_stmt)).all() by_type = {row[0]: row[1] for row in type_result} return {"total": total, "enabled": enabled, "disabled": total - enabled, "by_type": by_type} @router.post("/seed/{db_type}", response_model=List[AuditRuleResponse]) async def seed_audit_rules( db_type: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): """批量初始化预设审核规则种子数据。 支持4种数据库类型: - mysql: MySQL 专用规则 (60+ 条) - postgres: PostgreSQL 专用规则 (45+ 条) - redis: Redis 专用规则 (43+ 条) - mongodb: MongoDB 专用规则 (42+ 条) """ db_type_lower = db_type.lower() # 别名映射 db_type_aliases = { "postgresql": "postgres", "pg": "postgres", "mongo": "mongodb", } db_type_lower = db_type_aliases.get(db_type_lower, db_type_lower) if db_type_lower not in SEED_RULES_MAP: raise HTTPException( status_code=400, detail=f"不支持的数据库类型: {db_type},支持: {', '.join(SEED_RULES_MAP.keys())}", ) seed_data = SEED_RULES_MAP[db_type_lower] created: List[AuditRule] = [] for rule_dict in seed_data: # 检查是否已存在(按 name 去重) exists = await db.execute(select(AuditRule).where(AuditRule.name == rule_dict["name"])) if exists.scalar_one_or_none(): continue # 跳过已存在的规则 rule = AuditRule(**rule_dict) db.add(rule) created.append(rule) await db.commit() for rule in created: await db.refresh(rule) return created @router.get("/{rule_id}", response_model=AuditRuleResponse) async def get_audit_rule( rule_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin", "dba")), ): result = await db.execute(select(AuditRule).where(AuditRule.id == rule_id)) rule = result.scalar_one_or_none() if not rule: raise HTTPException(status_code=404, detail="规则不存在") return rule @router.put("/{rule_id}", response_model=AuditRuleResponse) async def update_audit_rule( rule_id: int, data: AuditRuleUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): result = await db.execute(select(AuditRule).where(AuditRule.id == rule_id)) rule = result.scalar_one_or_none() if not rule: raise HTTPException(status_code=404, detail="规则不存在") update_data = data.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(rule, key, value) await db.commit() await db.refresh(rule) return rule @router.delete("/{rule_id}") async def delete_audit_rule( rule_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): result = await db.execute(select(AuditRule).where(AuditRule.id == rule_id)) rule = result.scalar_one_or_none() if not rule: raise HTTPException(status_code=404, detail="规则不存在") await db.delete(rule) await db.commit() return {"message": "规则已删除"} @router.post("/{rule_id}/toggle", response_model=AuditRuleResponse) async def toggle_audit_rule( rule_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin", "dba")), ): result = await db.execute(select(AuditRule).where(AuditRule.id == rule_id)) rule = result.scalar_one_or_none() if not rule: raise HTTPException(status_code=404, detail="规则不存在") rule.is_enabled = not rule.is_enabled await db.commit() await db.refresh(rule) return rule @router.post("/seed-v2", response_model=List[AuditRuleResponse]) async def seed_v2_audit_rules( db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role("admin")), ): """批量初始化 SQL 审核引擎 V2 的所有规则种子数据(40+ 条规则)。 涵盖 DDL/DML/DQL/命名规范/性能/安全等多个维度, 跳过数据库中已存在的规则(按名称去重)。 """ created = await seed_all_rules(db) return created