sync: 同步 sql-platform-pro 最新代码 — 权限体系安全修复 v0.8.1
- P0: 实例访问控制全线接入权限组 (829测试) - P1: permissions字段激活 + 操作级权限检查 (843测试) - 前端: 删除数据权限页面, 权限组合并为唯一入口 - 新增: CodeMirror编辑器 + 查询模板 + 慢查询分析 - 新增: MongoDB管理页面 + Dashboard增强 - 修复: Pydantic V2迁移 + 前端TS错误修复
This commit is contained in:
1 parent
6fcd9465cf
commit
4c9427e546
105 files changed
+23680
-471
No files matched your search
@@ -0,0 +1,176 @@
|
||||
"""回滚 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,
|
||||
)
|
||||
Reference in new issue
Block a user