Initial commit: SQL audit platform v0.1.0

FastAPI backend with auth, user, instance, ticket, query, dashboard APIs.
React + TypeScript frontend with mock data UI (10 pages).
SQLite + async SQLAlchemy, JWT auth, role-based access control.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
loganandClaude Opus 4.7 committed 2026-05-22 11:11:57 +08:00
commit 439155c92e
290 files changed
+61950

No files matched your search

View File
Whitespace-only changes.
+79
View File
@@ -0,0 +1,79 @@
import asyncio
from typing import List, Dict, Any
from app.models.instance import Instance
from app.schemas.instance import ConnectionTestResult
class InstanceService:
@staticmethod
async def test_connection(instance: Instance) -> ConnectionTestResult:
try:
if instance.db_type == "mysql":
import aiomysql
conn = await asyncio.wait_for(
aiomysql.connect(
host=instance.host,
port=instance.port,
user=instance.username,
password=instance.password,
database=instance.database or None,
),
timeout=10,
)
async with conn.cursor() as cur:
await cur.execute("SELECT VERSION()")
version = (await cur.fetchone())[0]
conn.close()
return ConnectionTestResult(success=True, message="连接成功", version=str(version))
return ConnectionTestResult(success=False, message=f"暂不支持 {instance.db_type} 连接测试")
except asyncio.TimeoutError:
return ConnectionTestResult(success=False, message="连接超时")
except Exception as e:
return ConnectionTestResult(success=False, message=f"连接失败: {str(e)}")
@staticmethod
async def list_databases(instance: Instance) -> List[str]:
if instance.db_type != "mysql":
return []
import aiomysql
conn = await aiomysql.connect(
host=instance.host, port=instance.port,
user=instance.username, password=instance.password,
)
async with conn.cursor() as cur:
await cur.execute("SHOW DATABASES")
rows = await cur.fetchall()
conn.close()
return [row[0] for row in rows if row[0] not in ("information_schema", "mysql", "performance_schema", "sys")]
@staticmethod
async def list_tables(instance: Instance, database: str) -> List[Dict[str, Any]]:
if instance.db_type != "mysql":
return []
import aiomysql
conn = await aiomysql.connect(
host=instance.host, port=instance.port,
user=instance.username, password=instance.password,
database=database,
)
async with conn.cursor() as cur:
await cur.execute("SHOW TABLES")
tables = [row[0] for row in await cur.fetchall()]
result = []
for table in tables:
await cur.execute(f"SHOW TABLE STATUS LIKE '{table}'")
status = await cur.fetchone()
result.append({
"name": table,
"engine": status[1],
"rows": status[4],
"size": status[6],
"comment": status[17],
})
conn.close()
return result
+78
View File
@@ -0,0 +1,78 @@
import asyncio
import re
import time
from typing import Optional
from app.models.instance import Instance
from app.schemas.query import QueryResult
FORBIDDEN_KEYWORDS = ["DROP", "TRUNCATE", "DELETE", "UPDATE", "INSERT", "CREATE", "ALTER", "GRANT", "REVOKE"]
MAX_ROWS = 10000
class QueryService:
@staticmethod
async def execute_query(
instance: Instance, database: Optional[str], sql: str, user_id: int
) -> QueryResult:
if instance.db_type != "mysql":
raise ValueError(f"暂不支持 {instance.db_type} 查询")
upper_sql = sql.strip().upper()
for kw in FORBIDDEN_KEYWORDS:
if upper_sql.startswith(kw) or f" {kw} " in upper_sql:
raise ValueError(f"查询模式禁止执行 {kw} 操作")
if not re.search(r"LIMIT\s+\d+", upper_sql, re.IGNORECASE):
sql = sql.rstrip(";") + f" LIMIT {MAX_ROWS};"
import aiomysql
conn = await aiomysql.connect(
host=instance.host, port=instance.port,
user=instance.username, password=instance.password,
db=database or instance.database,
)
start = time.time()
try:
async with conn.cursor(aiomysql.DictCursor) as cur:
await asyncio.wait_for(cur.execute(sql), timeout=30)
rows = await cur.fetchall()
columns = [desc[0] for desc in cur.description] if cur.description else []
elapsed = int((time.time() - start) * 1000)
return QueryResult(
columns=columns,
rows=rows,
total=len(rows),
elapsed_ms=elapsed,
truncated=len(rows) >= MAX_ROWS,
)
except asyncio.TimeoutError:
raise ValueError("查询超时(30秒)")
finally:
conn.close()
@staticmethod
async def execute_dml(instance: Instance, database: str, sql: str) -> str:
if instance.db_type != "mysql":
raise ValueError(f"暂不支持 {instance.db_type} 执行")
import aiomysql
conn = await aiomysql.connect(
host=instance.host, port=instance.port,
user=instance.username, password=instance.password,
db=database,
autocommit=True,
)
try:
async with conn.cursor() as cur:
await asyncio.wait_for(cur.execute(sql), timeout=60)
affected = cur.rowcount
return f"执行成功,影响 {affected} 行"
except asyncio.TimeoutError:
raise ValueError("执行超时(60秒)")
finally:
conn.close()