P0-1 认证安全: - JWT secret 改为环境变量配置, 未设置则随机生成(重启失效) - 新增 LoginRateLimitMiddleware: 5次/分钟登录频率限制(滑动窗口) - 429 响应 + 前端 toast 提示 P0-2 操作审计日志: - 新增 OperationLog 模型 (user/username/action/resource/detail/ip) - 新增 AuditLogMiddleware 自动记录 POST/PUT/DELETE/PATCH 操作 - 新增 GET /audit-logs API (分页/过滤/统计) - 前端 AuditLogsPage 对接真实 API (字段/路径修正) P0-3 实例密码加密: - 已确认 crypto.py AES-256-GCM 加密存储, 无需修改 P1 前端错误提示: - 新增 useToast hook + ToastContainer 组件 - request.ts 拦截器: 4xx/5xx 错误 toast 显示后端 detail 信息 - 422 验证错误提取第一条 msg - 网络失败显示通用提示 1182 tests passed
150 lines
5.0 KiB
Python
150 lines
5.0 KiB
Python
import asyncio
|
||
from contextlib import asynccontextmanager
|
||
from datetime import datetime, timezone
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from sqlalchemy import select
|
||
|
||
from app.core.config import get_settings
|
||
from app.core.rate_limit import LoginRateLimitMiddleware
|
||
from app.core.audit_middleware import AuditLogMiddleware
|
||
from app.core.database import init_db, async_session
|
||
from app.core.security import hash_password
|
||
from app.models.user import User
|
||
from app.models.instance import Instance
|
||
from app.models.ticket import Ticket
|
||
from app.models.audit_log import AuditLog
|
||
from app.api.v1.router import api_router
|
||
|
||
settings = get_settings()
|
||
|
||
|
||
async def seed_workflows():
|
||
"""创建默认审批流程。"""
|
||
async with async_session() as db:
|
||
from app.services.workflow_seeds import seed_default_workflows
|
||
try:
|
||
await seed_default_workflows(db)
|
||
except Exception:
|
||
pass # 可能已存在
|
||
|
||
|
||
async def seed_data():
|
||
"""Create default users with different roles."""
|
||
from app.core.security import hash_password
|
||
|
||
default_users = [
|
||
{"username": "admin", "display_name": "超级管理员", "role": "admin", "password": "admin123"},
|
||
{"username": "dba", "display_name": "张三", "role": "dba", "password": "admin123"},
|
||
{"username": "auditor", "display_name": "老六", "role": "auditor", "password": "admin123"},
|
||
{"username": "developer", "display_name": "路人甲", "role": "developer", "password": "admin123"},
|
||
]
|
||
|
||
async with async_session() as db:
|
||
for u in default_users:
|
||
result = await db.execute(select(User).where(User.username == u["username"]))
|
||
if not result.scalar_one_or_none():
|
||
db.add(User(
|
||
username=u["username"],
|
||
display_name=u["display_name"],
|
||
role=u["role"],
|
||
hashed_password=hash_password(u["password"]),
|
||
))
|
||
await db.commit()
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
# Startup
|
||
from app.core.database import Base
|
||
# Import all models so create_all picks them up
|
||
import app.models.user # noqa
|
||
import app.models.instance # noqa
|
||
import app.models.ticket # noqa
|
||
import app.models.audit_log # noqa
|
||
import app.models.audit_rule # noqa
|
||
import app.models.announcement # noqa
|
||
import app.models.notification # noqa
|
||
import app.models.ldap_config # noqa
|
||
import app.models.backup # noqa
|
||
import app.models.instance_group # noqa
|
||
import app.models.masking_rule # noqa
|
||
import app.models.workflow # noqa
|
||
import app.models.notification_template # noqa
|
||
import app.models.scheduled_task # noqa
|
||
import app.models.schema_revision # noqa
|
||
import app.models.query_history # noqa
|
||
|
||
await init_db()
|
||
await seed_data()
|
||
await seed_workflows()
|
||
|
||
# 同步审核规则到数据库(首次将硬编码 config/severity 写入 DB)
|
||
try:
|
||
from app.core.database import async_session
|
||
async with async_session() as sync_db:
|
||
from app.services.audit_rule_sync import sync_rules_to_db
|
||
await sync_rules_to_db(sync_db)
|
||
except Exception as e:
|
||
import logging
|
||
logging.getLogger(__name__).warning("审核规则同步失败: %s", e)
|
||
|
||
# 确保所有种子设置项存在(含 executor 分组等新增项)
|
||
try:
|
||
from app.core.database import async_session
|
||
async with async_session() as seed_db:
|
||
from app.api.v1.endpoints.settings import _ensure_settings
|
||
await _ensure_settings(seed_db)
|
||
except Exception as e:
|
||
import logging
|
||
logging.getLogger(__name__).warning("种子设置同步失败: %s", e)
|
||
|
||
# 启动 APScheduler + 恢复定时工单
|
||
from app.services.ticket_scheduler import start_scheduler, recover_scheduled_tickets, shutdown_scheduler
|
||
start_scheduler()
|
||
recovered = await recover_scheduled_tickets()
|
||
if recovered > 0:
|
||
import logging
|
||
logging.getLogger(__name__).info("恢复了 %d 个定时工单", recovered)
|
||
|
||
yield
|
||
|
||
# Shutdown
|
||
shutdown_scheduler()
|
||
|
||
|
||
app = FastAPI(
|
||
title="SQL-Platform API",
|
||
version="1.0.0",
|
||
description="数据审核平台 API",
|
||
lifespan=lifespan,
|
||
docs_url="/docs" if settings.DEBUG else None,
|
||
)
|
||
|
||
# CORS: development allows *, production uses CORS_ORIGINS env var
|
||
_cors_origins = ["*"] if settings.DEBUG else [
|
||
o.strip() for o in settings.CORS_ORIGINS.split(",") if o.strip()
|
||
]
|
||
app.add_middleware(AuditLogMiddleware)
|
||
app.add_middleware(LoginRateLimitMiddleware)
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=_cors_origins,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
app.include_router(api_router)
|
||
|
||
|
||
@app.get("/health")
|
||
async def health_check():
|
||
return {"status": "ok", "version": "1.0.0", "time": datetime.now(timezone.utc).isoformat()}
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import uvicorn
|
||
uvicorn.run("app.main:app", host="0.0.0.0", port=9000, reload=True)
|