Files
opsxsql/backend/app/services/risk_evaluator.py
T
logan f6f9142ce8 feat: Phase2影响分析 — EXPLAIN预检+大表检测(简化版)+执行策略+风险评估table_sizes支持 (1141测试)
新增:
- explain_preview.py: EXPLAIN预检服务(MySQL/PG), 影响行数预估, 全表扫描检测
- ticket.executor: 三种执行策略(stop_on_error/continue/transaction)
- risk_evaluator: table_sizes参数, 大表ALTER/UPDATE/DELETE自动升级high
- test_phase2_features.py: 22个测试(EXPLAIN+执行策略+大表检测)

简化:
- 砍掉CTX-001上下文感知规则(复杂二次扫描), 合并大表检测到risk_evaluator
- 审核引擎保持纯静态分析, 不再导入_RULE_REGISTRY做二次扫描

模型:
- Ticket加exec_strategy字段
- TicketSqlItem模型
- schema加sql_type/exec_strategy/risk_level/workflow_id
2026-05-25 11:02:51 +08:00

199 lines
6.0 KiB
Python

"""工单风险等级自动评估。
根据 SQL 类型 + 操作对象 + 审核结果,自动计算 risk_level:
- low: SELECT / 简单 DML (有 WHERE + LIMIT)
- medium: 普通 DML / 低风险 DDL (CREATE TABLE)
- high: 大表 ALTER / 无 WHERE 的 UPDATE/DELETE / 多表 JOIN
- critical: DROP TABLE / TRUNCATE / FLUSHDB / FLUSHALL / dropDatabase
评估时机:工单创建时自动计算,写入 ticket.risk_level。
"""
import re
from typing import Optional
def evaluate_risk_level(
sql_content: str,
db_type: str = "mysql",
sql_type: str = "",
audit_findings: Optional[list] = None,
statement_count: int = 1,
table_sizes: Optional[dict] = None,
) -> str:
"""评估 SQL 的风险等级。
Args:
sql_content: SQL 文本(可能包含多条语句)
db_type: 数据库类型 mysql/postgres/redis/mongodb
sql_type: 前端传入的 SQL 类型标注 DDL/DML/DQL 等
audit_findings: 审核引擎返回的 findings 列表
statement_count: SQL 语句条数
table_sizes: EXPLAIN 预检返回的表大小 {table_name: row_count}
Returns:
risk_level: low / medium / high / critical
"""
sql_upper = sql_content.strip().upper()
# ── Redis 特殊处理 ──
if db_type == "redis":
return _evaluate_redis_risk(sql_upper)
# ── MongoDB 特殊处理 ──
if db_type == "mongodb":
return _evaluate_mongo_risk(sql_upper)
# ── MySQL / PostgreSQL ──
# 1. critical 级别 — 不可逆危险操作
critical_patterns = [
r"\bDROP\s+TABLE\b",
r"\bDROP\s+DATABASE\b",
r"\bDROP\s+SCHEMA\b",
r"\bTRUNCATE\s",
]
for pattern in critical_patterns:
if re.search(pattern, sql_upper):
return "critical"
# 2. high 级别 — 高风险操作
high_indicators = 0
# ALTER 大操作(但不包括 ADD INDEX 这类较小操作)
if re.search(r"\bALTER\s+TABLE\b", sql_upper):
alter_high = [
r"\bDROP\s+COLUMN\b",
r"\bMODIFY\s+COLUMN\b",
r"\bCHANGE\s+COLUMN\b",
r"\bDROP\s+INDEX\b",
r"\bDROP\s+PRIMARY\s+KEY\b",
r"\bCONVERT\s+TO\s+CHARACTER\s+SET\b",
r"\bRENAME\s+(TO|AS)\b",
]
for p in alter_high:
if re.search(p, sql_upper):
high_indicators += 1
break
# 无 WHERE 的 UPDATE / DELETE
if re.search(r"\bUPDATE\s", sql_upper) and not re.search(r"\bWHERE\b", sql_upper):
high_indicators += 1
if re.search(r"\bDELETE\s+FROM\b", sql_upper) and not re.search(r"\bWHERE\b", sql_upper):
high_indicators += 1
# 大量语句(>5条)
if statement_count > 5:
high_indicators += 1
# 审核结果有 ERROR 级别
if audit_findings:
errors = [f for f in audit_findings if f.get("level") == "error"]
if len(errors) >= 2:
high_indicators += 1
if high_indicators > 0:
return "high"
# 2.5 大表检测 — EXPLAIN 预检返回的 table_sizes
# ALTER/UPDATE/DELETE 涉及大表(>10万行)时升级为 high
if table_sizes:
max_rows = max(table_sizes.values()) if table_sizes else 0
if max_rows > 100_000 and re.search(r"\b(ALTER\s+TABLE|UPDATE|DELETE\s+FROM)\b", sql_upper):
return "high"
# 3. medium 级别 — 常规变更
medium_types = {"DDL", "DML", "SET", "HSET", "HMSET"}
if sql_type in medium_types:
return "medium"
# 有 WHERE 但没有 LIMIT 的 DELETE
if re.search(r"\bDELETE\s+FROM\b", sql_upper):
if not re.search(r"\bLIMIT\b", sql_upper):
return "medium"
# 任何 DDL
ddl_keywords = ["CREATE ", "ALTER ", "RENAME ", "GRANT ", "REVOKE ", "CREATE INDEX", "CREATE UNIQUE"]
for kw in ddl_keywords:
if kw in sql_upper:
return "medium"
# 4. low 级别 — 只读 / 安全操作
return "low"
def _evaluate_redis_risk(sql_upper: str) -> str:
"""Redis 命令风险评估。"""
critical_cmds = ["FLUSHDB", "FLUSHALL"]
for cmd in critical_cmds:
if sql_upper.startswith(cmd):
return "critical"
# 危险写操作
high_cmds = ["DEL ", "UNLINK ", "RENAME ", "FLUSHDB", "FLUSHALL"]
for cmd in high_cmds:
if sql_upper.startswith(cmd):
return "high"
# 一般写操作
write_cmds = ["SET ", "HSET ", "HMSET ", "LPUSH ", "RPUSH ", "SADD ", "ZADD ",
"INCR", "DECR", "EXPIRE ", "PEXPIRE "]
for cmd in write_cmds:
if sql_upper.startswith(cmd):
return "medium"
# 只读命令
return "low"
def _evaluate_mongo_risk(sql_upper: str) -> str:
"""MongoDB 操作风险评估。"""
sql_lower = sql_upper.lower()
critical_ops = ["dropdatabase", "drop database"]
for op in critical_ops:
if op in sql_lower:
return "critical"
high_ops = ["drop(", "dropcollection", "remove(", "deletemany"]
for op in high_ops:
if op in sql_lower:
return "high"
write_ops = ["insertone", "insertmany", "updateone", "updatemany", "deleteone",
"createindex", "create_collection"]
for op in write_ops:
if op in sql_lower:
return "medium"
return "low"
# ── 风险等级配置(用于前端展示 / 审批流匹配)──
RISK_LEVEL_CONFIG = {
"low": {
"label": "低风险",
"color": "#52c41a",
"description": "只读查询 / 安全操作",
"min_approvers": 1,
},
"medium": {
"label": "中风险",
"color": "#faad14",
"description": "常规数据变更 / DDL 操作",
"min_approvers": 1,
},
"high": {
"label": "高风险",
"color": "#fa8c16",
"description": "无 WHERE 删除/更新 / 大表 ALTER / 批量操作",
"min_approvers": 2,
},
"critical": {
"label": "极高风险",
"color": "#f5222d",
"description": "DROP TABLE / TRUNCATE / FLUSHDB 等不可逆操作",
"min_approvers": 2,
},
}