zyc 79368c11e2
All checks were successful
Build and Deploy Backend / build-and-deploy (push) Successful in 1m25s
Build and Deploy Web / build-and-deploy (push) Successful in 56s
fix db bug
2026-02-14 10:01:20 +08:00

184 lines
6.4 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.

"""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()