Files
opsxsql/backend/app/main.py
T
logan 344c519209 feat: P0生产加固 — 认证安全+操作审计+错误提示
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
2026-05-25 12:57:59 +08:00

150 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)