- P0: 实例访问控制全线接入权限组 (829测试) - P1: permissions字段激活 + 操作级权限检查 (843测试) - 前端: 删除数据权限页面, 权限组合并为唯一入口 - 新增: CodeMirror编辑器 + 查询模板 + 慢查询分析 - 新增: MongoDB管理页面 + Dashboard增强 - 修复: Pydantic V2迁移 + 前端TS错误修复
177 lines
5.2 KiB
Python
177 lines
5.2 KiB
Python
"""回滚 SQL API 路由 — 提供回滚 SQL 生成和预览功能。"""
|
|
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 get_current_user, require_role
|
|
from app.core.crypto import decrypt_password
|
|
from app.models.user import User
|
|
from app.models.instance import Instance
|
|
from app.models.ticket import Ticket
|
|
from app.schemas.rollback import (
|
|
RollbackPreviewRequest,
|
|
RollbackPreviewResponse,
|
|
RollbackGenerateResponse,
|
|
RollbackGenerateRequest,
|
|
)
|
|
from app.services.rollback_generator import RollbackGenerator, RollbackGeneratorError
|
|
|
|
router = APIRouter(tags=["回滚 SQL"])
|
|
|
|
|
|
async def _get_ticket(db: AsyncSession, ticket_id: int) -> Ticket:
|
|
"""获取工单并检查是否存在。
|
|
|
|
Args:
|
|
db: 数据库会话
|
|
ticket_id: 工单ID
|
|
|
|
Returns:
|
|
工单对象
|
|
|
|
Raises:
|
|
HTTPException: 工单不存在
|
|
"""
|
|
result = await db.execute(select(Ticket).where(Ticket.id == ticket_id))
|
|
ticket = result.scalar_one_or_none()
|
|
if not ticket:
|
|
raise HTTPException(status_code=404, detail="工单不存在")
|
|
return ticket
|
|
|
|
|
|
async def _get_instance(db: AsyncSession, instance_id: int) -> Instance:
|
|
"""获取实例并检查是否存在。
|
|
|
|
Args:
|
|
db: 数据库会话
|
|
instance_id: 实例ID
|
|
|
|
Returns:
|
|
实例对象
|
|
|
|
Raises:
|
|
HTTPException: 实例不存在
|
|
"""
|
|
result = await db.execute(select(Instance).where(Instance.id == instance_id))
|
|
instance = result.scalar_one_or_none()
|
|
if not instance:
|
|
raise HTTPException(status_code=404, detail="数据源不存在")
|
|
return instance
|
|
|
|
|
|
@router.post("/tickets/{ticket_id}/generate-rollback", response_model=RollbackGenerateResponse)
|
|
async def generate_rollback_sql(
|
|
ticket_id: int,
|
|
data: RollbackGenerateRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(require_role("admin", "dba")),
|
|
):
|
|
"""为工单生成回滚 SQL。
|
|
|
|
根据工单中的 SQL 内容,自动生成对应的回滚 SQL。
|
|
支持 INSERT/UPDATE/DELETE 三种 DML 操作。
|
|
|
|
Args:
|
|
ticket_id: 工单ID
|
|
data: 生成选项(是否自动保存)
|
|
db: 数据库会话
|
|
current_user: 当前用户
|
|
|
|
Returns:
|
|
生成的回滚 SQL 信息
|
|
|
|
Raises:
|
|
HTTPException: 工单不存在、无权访问或生成失败
|
|
"""
|
|
ticket = await _get_ticket(db, ticket_id)
|
|
|
|
# 获取关联的数据源实例
|
|
instance = await _get_instance(db, ticket.instance_id)
|
|
instance.password = decrypt_password(instance.password)
|
|
|
|
# 生成回滚 SQL
|
|
generator = RollbackGenerator()
|
|
try:
|
|
rollback_sql = await generator.generate(
|
|
instance, ticket.target_database, ticket.sql_content
|
|
)
|
|
except RollbackGeneratorError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"回滚 SQL 生成失败: {str(e)}")
|
|
|
|
# 自动保存到工单
|
|
saved = False
|
|
if data.auto_save:
|
|
ticket.rollback_sql = rollback_sql
|
|
await db.commit()
|
|
saved = True
|
|
|
|
# 检测 SQL 类型
|
|
from app.services.sql_analyzer import SQLAnalyzer
|
|
analyzer = SQLAnalyzer()
|
|
sql_type = analyzer._detect_sql_type(ticket.sql_content)
|
|
|
|
return RollbackGenerateResponse(
|
|
ticket_id=ticket_id,
|
|
original_sql=ticket.sql_content,
|
|
rollback_sql=rollback_sql,
|
|
sql_type=sql_type.value if sql_type else None,
|
|
saved=saved,
|
|
)
|
|
|
|
|
|
@router.post("/rollback/preview", response_model=RollbackPreviewResponse)
|
|
async def preview_rollback_sql(
|
|
req: RollbackPreviewRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""预览回滚 SQL(不保存)。
|
|
|
|
为给定的 SQL 语句生成回滚 SQL 预览,不执行实际数据库操作,
|
|
也不保存到任何工单。
|
|
|
|
Args:
|
|
req: 预览请求(实例ID、数据库、SQL)
|
|
db: 数据库会话
|
|
current_user: 当前用户
|
|
|
|
Returns:
|
|
回滚 SQL 预览
|
|
|
|
Raises:
|
|
HTTPException: 实例不存在或无权限
|
|
"""
|
|
instance = await _get_instance(db, req.instance_id)
|
|
instance.password = decrypt_password(instance.password)
|
|
|
|
generator = RollbackGenerator()
|
|
try:
|
|
rollback_sql = await generator.preview(instance, req.database, req.sql)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"回滚 SQL 预览失败: {str(e)}")
|
|
|
|
# 检测 SQL 类型
|
|
from app.services.sql_analyzer import SQLAnalyzer
|
|
analyzer = SQLAnalyzer()
|
|
sql_type = analyzer._detect_sql_type(req.sql)
|
|
|
|
warning = None
|
|
if rollback_sql.startswith("--"):
|
|
# 提取警告信息
|
|
lines = rollback_sql.split("\n")
|
|
warning_lines = [l for l in lines if l.startswith("--") and "无法" in l]
|
|
if warning_lines:
|
|
warning = warning_lines[0].replace("-- ", "")
|
|
|
|
return RollbackPreviewResponse(
|
|
original_sql=req.sql,
|
|
rollback_sql=rollback_sql,
|
|
sql_type=sql_type.value if sql_type else None,
|
|
warning=warning,
|
|
)
|