184 lines
6.4 KiB
Python
184 lines
6.4 KiB
Python
"""AirLabs Project —— 主入口"""
|
||
from dotenv import load_dotenv
|
||
load_dotenv()
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.staticfiles import StaticFiles
|
||
from fastapi.responses import FileResponse
|
||
from database import engine, Base
|
||
from models import (
|
||
User, Role, PhaseGroup, BUILTIN_ROLES, COST_PERM_MIGRATION,
|
||
Project, ProjectMilestone, DEFAULT_MILESTONES
|
||
)
|
||
from auth import hash_password
|
||
import os
|
||
import logging
|
||
|
||
logging.basicConfig(level=logging.INFO)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 创建所有表
|
||
Base.metadata.create_all(bind=engine)
|
||
|
||
# ── 手动迁移:为旧 SQLite 数据库补充 role_id 列 ──
|
||
from config import DATABASE_URL as _db_url
|
||
if _db_url.startswith("sqlite"):
|
||
import sqlalchemy
|
||
with engine.connect() as conn:
|
||
cols = [r[1] for r in conn.execute(sqlalchemy.text("PRAGMA table_info('users')"))]
|
||
if "role_id" not in cols:
|
||
conn.execute(sqlalchemy.text("ALTER TABLE users ADD COLUMN role_id INTEGER"))
|
||
conn.commit()
|
||
logger.info("[MIGRATE] added column users.role_id")
|
||
|
||
app = FastAPI(title="AirLabs Project", version="1.0.0")
|
||
|
||
# CORS
|
||
from config import CORS_ORIGINS
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=CORS_ORIGINS,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# 注册路由
|
||
from routers.auth import router as auth_router
|
||
from routers.users import router as users_router
|
||
from routers.projects import router as projects_router
|
||
from routers.submissions import router as submissions_router
|
||
from routers.costs import router as costs_router
|
||
from routers.dashboard import router as dashboard_router
|
||
from routers.roles import router as roles_router
|
||
try:
|
||
from routers.reports import router as reports_router
|
||
except ImportError as e:
|
||
reports_router = None
|
||
logging.warning(f"[路由] reports 模块加载失败: {e}")
|
||
|
||
app.include_router(auth_router)
|
||
app.include_router(users_router)
|
||
app.include_router(projects_router)
|
||
app.include_router(submissions_router)
|
||
app.include_router(costs_router)
|
||
app.include_router(dashboard_router)
|
||
app.include_router(roles_router)
|
||
if reports_router:
|
||
app.include_router(reports_router)
|
||
|
||
# 前端静态文件
|
||
frontend_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "frontend", "dist")
|
||
if os.path.exists(frontend_dir):
|
||
app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dir, "assets")), name="assets")
|
||
|
||
@app.get("/{full_path:path}")
|
||
async def serve_frontend(full_path: str):
|
||
file_path = os.path.join(frontend_dir, full_path)
|
||
if os.path.isfile(file_path):
|
||
return FileResponse(file_path)
|
||
return FileResponse(os.path.join(frontend_dir, "index.html"))
|
||
|
||
|
||
@app.on_event("startup")
|
||
async def start_scheduler():
|
||
"""启动定时任务调度器"""
|
||
try:
|
||
from services.scheduler_service import setup_scheduler
|
||
setup_scheduler()
|
||
except Exception as e:
|
||
logger.warning(f"[定时任务] 启动失败(不影响核心功能): {e}")
|
||
|
||
|
||
@app.on_event("shutdown")
|
||
async def stop_scheduler():
|
||
"""关闭定时任务调度器"""
|
||
try:
|
||
from services.scheduler_service import scheduler
|
||
scheduler.shutdown(wait=False)
|
||
logger.info("[定时任务] 已关闭")
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
@app.on_event("startup")
|
||
def init_roles_and_admin():
|
||
"""首次启动时创建内置角色和默认管理员"""
|
||
from database import SessionLocal
|
||
db = SessionLocal()
|
||
try:
|
||
# 初始化内置角色
|
||
for role_name, role_def in BUILTIN_ROLES.items():
|
||
existing = db.query(Role).filter(Role.name == role_name).first()
|
||
if not existing:
|
||
role = Role(
|
||
name=role_name,
|
||
description=role_def["description"],
|
||
permissions=role_def["permissions"],
|
||
is_system=1,
|
||
)
|
||
db.add(role)
|
||
print(f"[OK] created role: {role_name}")
|
||
db.commit()
|
||
|
||
# 迁移旧成本权限 → 细分权限
|
||
old_cost_perms = set(COST_PERM_MIGRATION.keys())
|
||
for role in db.query(Role).all():
|
||
perms = list(role.permissions or [])
|
||
changed = False
|
||
for old_perm, new_perms in COST_PERM_MIGRATION.items():
|
||
if old_perm in perms:
|
||
perms.remove(old_perm)
|
||
for np in new_perms:
|
||
if np not in perms:
|
||
perms.append(np)
|
||
changed = True
|
||
if changed:
|
||
role.permissions = perms
|
||
print(f"[MIGRATE] upgraded cost permissions for role: {role.name}")
|
||
db.commit()
|
||
|
||
# 为已有项目补充默认里程碑
|
||
for proj in db.query(Project).all():
|
||
has_ms = db.query(ProjectMilestone).filter(
|
||
ProjectMilestone.project_id == proj.id
|
||
).first()
|
||
if not has_ms:
|
||
for ms in DEFAULT_MILESTONES:
|
||
db.add(ProjectMilestone(
|
||
project_id=proj.id,
|
||
name=ms["name"],
|
||
phase=PhaseGroup(ms["phase"]),
|
||
sort_order=ms.get("sort_order", 0),
|
||
))
|
||
print(f"[MIGRATE] added default milestones for project: {proj.name}")
|
||
db.commit()
|
||
|
||
# 迁移:为旧用户补充默认 role_id(成员角色)
|
||
member_role = db.query(Role).filter(Role.name == "成员").first()
|
||
if member_role:
|
||
orphans = db.query(User).filter(User.role_id.is_(None)).all()
|
||
for u in orphans:
|
||
u.role_id = member_role.id
|
||
print(f"[MIGRATE] assigned default role '成员' to user: {u.username}")
|
||
if orphans:
|
||
db.commit()
|
||
|
||
# 创建默认管理员(关联超级管理员角色)
|
||
admin_role = db.query(Role).filter(Role.name == "超级管理员").first()
|
||
if admin_role and not db.query(User).filter(User.username == "admin").first():
|
||
owner = User(
|
||
username="admin",
|
||
password_hash=hash_password("admin123"),
|
||
name="管理员",
|
||
phase_group=PhaseGroup.PRODUCTION,
|
||
role_id=admin_role.id,
|
||
monthly_salary=0,
|
||
)
|
||
db.add(owner)
|
||
db.commit()
|
||
print("[OK] default admin created: admin / admin123")
|
||
finally:
|
||
db.close()
|