M4(文风): style-auditor 双轨(提取指纹/漂移第四审)+ jobs 长任务框架(zombie reaper) + 回炉 refine + GET /style read-back。 M5(生成+扩展): worldbuilder/character-gen(入库 continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变); 网关多 provider 回退链/熔断/能力降级(Anthropic/Gemini 适配器);Skill registry + 表权限沙箱 + 规则; 前端 角色生成器/世界观/Codex/规则页/技能库/⌘K 命令面板。 K1(Kimi Code 订阅接入): OAuth device-flow(kimi-code)+ 静态 Console key(kimi-code-key)两路径; coding 端点 KimiCLI 伪造头(实测 UA allow-list 门禁,缺则 403)+ JSON 模式结构化(thinking ⊥ tool_choice)。 本地联调修复: CORS 中间件;assemble 注入 premise+「写第N章」指令(修空 prompt 400); GET /outline·/draft read-back + 大纲/工作台/审稿页重载;写页 client/server 常量边界 + notFound 健壮化; 字数 toLocaleString locale 水合;审稿页终稿从已存草稿 seed(修 accept 422)。 门禁: backend ruff/mypy(157)/alembic 无漂移/pytest 451 · frontend lint/tsc/vitest/build。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
3.2 KiB
Python
94 lines
3.2 KiB
Python
"""FastAPI 应用入口(ARCH §7)。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from collections.abc import AsyncIterator
|
||
from contextlib import asynccontextmanager
|
||
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.responses import JSONResponse
|
||
from ww_config import get_settings
|
||
from ww_core.domain.job_repo import SqlJobRepo
|
||
from ww_db import get_sessionmaker
|
||
from ww_shared import AppError, ErrorBody, ErrorEnvelope
|
||
|
||
from ww_api.logging_config import configure_logging, get_logger
|
||
from ww_api.middleware import request_id_middleware
|
||
from ww_api.routers import (
|
||
foreshadow,
|
||
generation,
|
||
health,
|
||
jobs,
|
||
kimi_oauth,
|
||
outline,
|
||
projects,
|
||
rules,
|
||
settings_providers,
|
||
style,
|
||
)
|
||
from ww_api.services.project_deps import seed_stub_user
|
||
|
||
configure_logging()
|
||
log = get_logger("ww.api")
|
||
|
||
|
||
@asynccontextmanager
|
||
async def _lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||
# 幂等 seed 单用户 stub——所有 owner_id FK 依赖它(见 memory/gotchas)。
|
||
async with get_sessionmaker()() as session:
|
||
await seed_stub_user(session)
|
||
# zombie reaper(M4-d / §7.4 缓解):进程重启会丢未跑完的 BackgroundTask,把残留
|
||
# status=running 的 job 标 failed(让用户看到失败可重试,而非进度条永转)。自 commit、幂等。
|
||
async with get_sessionmaker()() as session:
|
||
reaped = await SqlJobRepo(session).reap_zombies()
|
||
if reaped:
|
||
log.info("job_zombies_reaped", count=reaped)
|
||
yield
|
||
|
||
|
||
def create_app() -> FastAPI:
|
||
app = FastAPI(title="网文创作工作流 API", version="0.0.0", lifespan=_lifespan)
|
||
# CORS:前端(Next.js)与 API 分端口/跨源,浏览器端 `api.POST/PUT` 需 CORS 放行
|
||
# (RSC 服务端取数同源、无需 CORS,故此前同进程测试从未暴露此缺口)。原型单用户、
|
||
# 本地开发:放行 localhost:3000;可经 env `CORS_ORIGINS`(逗号分隔)覆盖。
|
||
settings = get_settings()
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=settings.cors_origins,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
app.middleware("http")(request_id_middleware)
|
||
|
||
@app.exception_handler(AppError)
|
||
async def _app_error_handler(request: Request, exc: AppError) -> JSONResponse:
|
||
request_id = getattr(request.state, "request_id", None)
|
||
log.warning("app_error", code=exc.code, message=exc.message)
|
||
envelope = ErrorEnvelope(
|
||
error=ErrorBody(
|
||
code=exc.code,
|
||
message=exc.message,
|
||
details=exc.details,
|
||
request_id=request_id,
|
||
)
|
||
)
|
||
return JSONResponse(status_code=exc.http_status, content=envelope.model_dump())
|
||
|
||
app.include_router(health.router)
|
||
app.include_router(jobs.router)
|
||
app.include_router(projects.router)
|
||
app.include_router(foreshadow.router)
|
||
app.include_router(outline.router)
|
||
app.include_router(rules.router)
|
||
app.include_router(style.router)
|
||
app.include_router(generation.router)
|
||
app.include_router(generation.skills_router)
|
||
app.include_router(settings_providers.router)
|
||
app.include_router(kimi_oauth.router)
|
||
return app
|
||
|
||
|
||
app = create_app()
|