| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- """FastAPI 应用入口"""
- import os
- import sys
- from contextlib import asynccontextmanager
- from pathlib import Path
- from fastapi import FastAPI, Request
- from fastapi.middleware.cors import CORSMiddleware
- from fastapi.responses import JSONResponse
- from fastapi.staticfiles import StaticFiles
- from app.config import get_config, Config
- from app.database import init_db, get_connection
- @asynccontextmanager
- async def lifespan(app: FastAPI):
- """应用生命周期:启动时初始化,关闭时清理"""
- # 启动
- config = app.state.config
- config.data_dir.mkdir(parents=True, exist_ok=True)
- config.problems_dir.mkdir(parents=True, exist_ok=True)
- config.papers_dir.mkdir(parents=True, exist_ok=True)
- config.backups_dir.mkdir(parents=True, exist_ok=True)
- init_db(config.db_path)
- print(f"✓ 数据库已初始化: {config.db_path}")
- print(f"✓ API Token: {config.token}")
- print(f"✓ 服务启动在 http://{config.host}:{config.port}")
- yield
- def create_app(config: Config) -> FastAPI:
- app = FastAPI(
- title="题库PLUS",
- description="C/S 架构数学题库管理系统",
- version="1.0.0",
- lifespan=lifespan,
- )
- app.state.config = config
- # CORS
- app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
- # 全局异常处理
- @app.exception_handler(Exception)
- async def global_exception_handler(request: Request, exc: Exception):
- import traceback
- traceback.print_exc()
- return JSONResponse(
- status_code=500,
- content={"error": {"code": "internal_error", "message": str(exc)}},
- )
- # 注册路由
- from app.routers import auth, problems, import_, export_, knowledge, carts, admin
- app.include_router(auth.router, prefix="/api", tags=["auth"])
- app.include_router(problems.router, prefix="/api", tags=["problems"])
- app.include_router(import_.router, prefix="/api", tags=["import"])
- app.include_router(export_.router, prefix="/api", tags=["export"])
- app.include_router(knowledge.router, prefix="/api", tags=["knowledge"])
- app.include_router(carts.router, prefix="/api", tags=["carts"])
- app.include_router(admin.router, prefix="/api", tags=["admin"])
- # Flutter Web 静态文件
- web_dir = Path("web")
- if web_dir.exists() and any(web_dir.iterdir()):
- app.mount("/", StaticFiles(directory=str(web_dir), html=True), name="web")
- return app
- def main():
- """CLI 入口"""
- if len(sys.argv) < 2:
- print("用法: tiku-server <命令> [参数]")
- print()
- print("命令:")
- print(" serve 启动服务")
- print(" serve --restart 启动服务(崩溃/更新后自动重启)")
- print(" knowledge build 重建知识点编码")
- print(" rebuild-index 手动增量索引")
- print(" stats 终端统计")
- print(" backup 手动备份")
- print(" backup --remote 推送到 WebDAV")
- print(" token 显示/重新生成 API token")
- print(" token regenerate 重新生成 API token")
- sys.exit(1)
- cmd = sys.argv[1]
- config_path = os.environ.get("TIKU_CONFIG", "config.yaml")
- cfg = get_config(config_path)
- if cmd == "serve":
- import uvicorn
- auto_restart = len(sys.argv) > 2 and sys.argv[2] == "--restart"
- def run():
- uvicorn.run(
- create_app(cfg),
- host=cfg.host,
- port=cfg.port,
- log_level="info",
- )
- if auto_restart:
- import time as _time
- while True:
- print(f"{_time.strftime('%Y-%m-%d %H:%M:%S')} 启动服务端...")
- run()
- print(f"{_time.strftime('%Y-%m-%d %H:%M:%S')} 服务端退出,2 秒后重启...")
- _time.sleep(2)
- else:
- run()
- elif cmd == "knowledge":
- if len(sys.argv) > 2 and sys.argv[2] == "build":
- from app.services.knowledge import build_knowledge
- build_knowledge(cfg)
- elif cmd == "rebuild-index":
- from app.services.indexer import incremental_update
- incremental_update(cfg)
- elif cmd == "stats":
- from app.services.indexer import show_stats
- show_stats(cfg)
- elif cmd == "backup":
- remote = len(sys.argv) > 2 and sys.argv[2] == "--remote"
- from app.services.backup import manual_backup
- manual_backup(cfg, remote=remote)
- elif cmd == "token":
- action = sys.argv[2] if len(sys.argv) > 2 else "show"
- if action == "regenerate":
- new_token = cfg.regenerate_token()
- print(f"新 Token: {new_token}")
- else:
- print(f"Token: {cfg.token}")
- else:
- print(f"未知命令: {cmd}")
- sys.exit(1)
- if __name__ == "__main__":
- main()
|