Skip to content

feat(backend): 后端全栈实现——基础设施 + 领域模块 + 用户认证 + API 契约 - #75

Open
xiaocheny214 wants to merge 88 commits into
1024XEngineer:mainfrom
xiaocheny214:feat/backend-dev
Open

feat(backend): 后端全栈实现——基础设施 + 领域模块 + 用户认证 + API 契约#75
xiaocheny214 wants to merge 88 commits into
1024XEngineer:mainfrom
xiaocheny214:feat/backend-dev

Conversation

@xiaocheny214

@xiaocheny214 xiaocheny214 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

概述

本 PR 实现 Windup 后端完整能力:基础设施层、领域业务层、用户认证体系、生成管线,以及配套的 Docker 部署与测试覆盖。

新增能力

基础设施层(framework)

  • PostgreSQL 连接(SQLAlchemy 2.x + psycopg3)
  • Redis 连接(验证码 / refresh_token / 限流)
  • 七牛 Kodo 对象存储(上传/删除/签名 URL)
  • AI Provider 配置(OpenAI-compatible)
  • 视频 i2v Provider(qnaigc Kling o3)
  • 邮件发送抽象(SMTP)

领域业务层(app/server)

  • 项目模块:CRUD + 约束配置(视角、尺寸、风格)+ 唯一约束
  • 角色模块:CRUD + outfit → action → frame 嵌套数据 + name 字段
  • 生成模块:图片生成 + 动作生成 + 任务轮询 + 后台执行
  • 用户模块:注册/登录/JWT 鉴权/邮箱验证码/登出/刷新 token/改密
  • Agent 模块:SSE 会话管理骨架
  • WorkflowRun 模块:JSONB 节点树读写

API 层(web)

  • 统一响应格式 Response[T] / ListResponse[T]
  • 全局异常处理(BizException 继承体系)
  • JWT 鉴权中间件(白名单放行 + request.state.current_user 注入)
  • 接口限流中间件(Redis 滑动窗口 + 降级策略)

部署与 CI

  • Docker Compose:PostgreSQL + Redis + Backend
  • init.sql 建表脚本(含唯一约束)
  • GitHub Actions:前端 CI + 命名规范检查

安全修复

问题 修复
middleware 层 BizException 跳过 ExceptionMiddleware 改为直接返回 JSONResponse
project API 信任客户端 user_id,可越权 从 JWT 取 + 归属校验
generation API 同上 + project_id 无归属校验 同上 + project 归属校验
create_character 漏传 name,名称丢失 补传参数
docker-compose 缺 Redis,认证不可用 添加 Redis 服务
init.sql 缺唯一约束,生产可重名 补充 UNIQUE 约束

测试覆盖

  • test_user_service.py:22 用例(注册/登录/验证码/登出/刷新/改密/限流)
  • test_project_api.py:9 用例(CRUD + 权限归属)
  • test_character_api.py:3 用例(命名持久化)
  • test_generation_orchestration.py:生成编排
  • test_sufy_video_download.py:视频下载重试

关联 Issue

xiaocheny214 and others added 30 commits July 23, 2026 11:46
Describe the backend package layout (common/framework/app) and the server domain module split: user, project, asset, character with action/character_template/wearable subdomains, generation and media. All modules currently define abstract interfaces only.
Add the shared response/exception kernel in windup_common: Response/ListResponse unified bodies (HTTP always 200, business code in body), BizException base, ModelException for LLM calls, and the BizCode/ModelErrorType enums. Add the app-level global exception handlers that convert these into Response.fail.
Add windup_framework infrastructure abstractions: SQLAlchemy db base/session, config loaders (database/provider/storage), LLM provider protocols (chat/image/video) and Kodo object storage.
Add abstract service interfaces and domain models for the server modules: user, project, asset, character (with action/character_template/wearable subdomains), generation and media. Drop the now-obsolete .gitkeep placeholders.
Drop the placeholder health router and its registration in create_app; the web layer now revolves around the global exception handlers.
Expose the windup CLI entrypoint (windup_app.bootstrap.app:main) and add python-multipart for form/file uploads; refresh uv.lock accordingly.
Point the sub-entity references at the nested character subpackages (character.action / character_template / wearable) instead of the old top-level paths.
Remove unused imports flagged by ruff F401 in generation/interface.py and user/model.py.
将 validate-branch 与 validate-commits 从 backend.yml 拆到独立的 naming.yml。命名规范门禁不局限于 backend 范畴,应作为仓库级 CI 单独维护(PR review:@minorcell 建议)。

backend.yml 仅保留 lint-and-test;naming.yml 用独立 concurrency group 避免与 backend 共用 group 互相取消。
Add SqlAlchemyProjectService inheriting ProjectService; implements
create/get/list/delete plus project_name_exists against SQLAlchemy.

- interface: reconcile ProjectService to session-per-call; session is
  passed per request via get_session, create_project takes **fields.
- service: stateless impl as a module-level `service` singleton; flush
  only, commit/rollback handled by get_session; list ordered by id desc.
- model: id uses BigInteger().with_variant(Integer, "sqlite") so the
  in-memory SQLite test DB autoincrements; Postgres unchanged.
- api/bootstrap: import service from the service module, register
  project_router, add a print_banner startup stub.
Add SQLite in-memory test fixtures and project module tests.

- conftest: SQLite in-memory fixtures (StaticPool, single connection)
  with no Docker Postgres dependency; get_session override for the API
  TestClient.
- test_project_service: 14 unit tests for create/exists/get/list/delete
  covering the unique constraint, pagination, id-desc ordering and user
  filter.
- test_project_api: 9 end-to-end tests via TestClient covering CRUD and
  the unified response contract (HTTP 200, code in body, 400/404).
Reflect character/media/generation/user module redesigns in the split
document.
Replace strategy pattern with typed submit methods.

- model: add CharacterImageOutput/CharacterActionOutput/CharacterActionFrame
  as typed task results, remove generic GenerationResult
- interface: split submit into generate_character_image and
  generate_character_action with typed inputs
- document SSE streaming flow (GET /generation/tasks/{id}/stream)
  replacing frontend polling
Remove OAuth abstract methods and models from UserService.

Commented-out methods: get_oauth_authorize_url, login_by_oauth,
bind_oauth, get_oauth_bindings. Related imports OAuthCallbackInput and
UserOAuth removed from interface. The OAuth region is preserved as a
placeholder for future extension.
Add ObjectStorageMediaService implementing MediaService.

- service: upload to Kodo via KodoStorage adapter, auto-generated
  object keys with category prefix, no user filenames in keys
- app: register media router (POST /media/upload) in create_app
ci: split naming gate into its own workflow
feat:project module
跨层契约(common,无内部依赖):ActionType/GenRoute/CharacterCard/ActionSpec/AssetPackageRef。ai_engine 与 app 均依赖此。Refs 1024XEngineer#53。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SufyVideoProvider(kling i2v,OpenAI 兼容异步 /videos)+ OnnxU2NetMatteProvider(onnxruntime 直跑 u2netp,替代 rembg 在 3.12 无轮子的 numba 链)+ 能力接口(Image/Video/MatteProvider)。同时声明 framework 代码实际 import 的依赖(langchain-openai/onnxruntime/qiniu/pillow/numpy)——补齐 1024XEngineer#64 机器审指出的缺依赖。Refs 1024XEngineer#53 #35。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
填充空骨架:slicing(抽帧 + 周期/一次性选帧)、strategy(ROUTE_MATRIX 分流 + VideoFrameStrategy 图生视频)、prompt(walk/jump/attack/idle 提示词库)、postprocess(像素化 + 脚线对齐 + 打包 + 逐帧时长)、generator 串联。MVP:root_motion 暂不做(character_data.frames 无该字段,见 1024XEngineer#63);graph/ 留占位非本 PR。Refs 1024XEngineer#53 #35 #21。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove dead storage module (empty __init__.py and kodo.py) from framework
- Implement actual Qiniu Kodo SDK upload in ObjectStorageMediaService
- Move MediaCategory to windup_common.enums.media, remove duplicate from media/model
- Update media __init__ to import MediaCategory from common
Move qiniu import inside upload() so module collection does not fail
when qiniu is not installed (e.g. CI test runs).
按 MVP 边界(与作者对齐):CharacterGeneratorPort.generate(card, action, master, progress) → GeneratedAction(frames/durations/fps);母版由 server 以 bytes 传入,不再经 ArtifactStore;去掉 sprite sheet 打包与存储引用(上传/落库/导出在 server/export 侧)。补 VideoFrameStrategy.derive 离线端到端测试(monkeypatch 抽帧,不解码 mp4/不联网)。Refs 1024XEngineer#53。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(media): implement Qiniu upload and deduplicate MediaCategory

@johnnyzhang-eng johnnyzhang-eng left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

补几条 08:18 那轮未覆盖的。与那轮重合的不重复,但跨用户越权(project.py:75 / generation.py:185)同属本次必须处理的范围。

以下均在 4ee4236 上核实。

已由 xiaocheny214#35 处理,不必再改(Refs #143):
router 注册被删除、CORS 通配配 credentials。两条属机械修复无设计取舍,我直接改了。
下面四条涉及设计决策,留给作者。

P0 · num_images / num_frames 无上限,直通付费循环

generation.py:88,101le=executor.py:367for _ in range(max(1, input.num_images)),循环体是付费 gen_image

实测 num_images=100000 被接受、原样落库。ratelimit.py:92 在 Redis 不可用时 return await call_next(request),即整体放行,每分钟 60 次的兜底也没有。

P0 · daemon 线程 + 无优雅退出

generation.py:149daemon=True 线程;bootstrap/app.py:54-57_lifespanyield 后无任何处理(docstring 写「关闭时无特殊处理」)。backend/packages/atexit|signal\.|\.join\(\)|on_shutdown|add_event_handler 零命中。

后果:每次 deploy / reload 杀掉正在跑的 i2v。此时 RUNNING 已 commit、费用已产生,COMPLETED 永远不会写入,且重启后无扫描逻辑回收。生成耗时数分钟,轮询上限 30 分钟(sufy.py:53-61),窗口不小。

P1 · 起线程失败时静默返回 200

_dispatch_after_commitafter_commitget_session teardown 时触发,此刻响应已生成。Thread.start()RuntimeError 时客户端已拿到 200「任务已提交」+ task id,DB 里一行 pending 无人执行,前端会一直轮询。

P1 · 生成全程独占一条 DB 连接

executor.py:165_load_constraints 发 SELECT(autobegin),:166_produce_action 随即跑数分钟,其间不 commit / close。实测生成期间 pool.checkedout() 恒为 1。

config/database.py:28-29pool_size=5, max_overflow=10,即 15 条封顶。15 个并发生成即耗尽连接池,此后所有 HTTP 请求(含前端轮询任务状态)阻塞在等连接,表现为整站假死而非生成变慢。run_image_task 同(:302-303)。


以上四条修完(或说明为何不改)我复核后撤销本次 request changes。

…are 层级问题

BaseHTTPMiddleware 中 raise BizException 会跳过内部 ExceptionMiddleware,
被最外层 ServerErrorMiddleware 兜底捕获,返回 INTERNAL_ERROR 而非预期业务码。
改为在 dispatch 内直接构造 JSONResponse 返回。
- POST /projects: user_id 从 request.state.current_user 取
- GET /projects: 移除 user_id query param,强制只查自己的项目
- GET /projects/{id}: 加归属校验,非本人项目返回 404
- DELETE /projects/{id}: 同上
- ProjectCreate 移除 user_id 字段,ProjectOut 独立声明
- POST /generation/image: user_id 从 JWT 取,校验 project 归属
- POST /generation/action: 同上
- GET /generation/tasks/{task_id}: 改用 get_task_by_user 校验归属
- 请求模型移除 user_id 字段
CharacterCreate.name 未传入 service.create_character(),导致返回 name=None。
补上 name=body.name 并新增 3 个集成测试覆盖命名场景。
后端验证码/refresh_token 依赖 Redis,原 compose 只有 Postgres。
新增 redis:7-alpine 服务(含健康检查),backend depends_on 等待就绪,
环境变量 REDIS_URL=redis://redis:6379/0。
ORM 声明了 UniqueConstraint(user_id, project_name),但 init.sql 建表时遗漏。
生产 Postgres 并发创建同名项目不会触发 IntegrityError,API 兜底失效。
补上 CONSTRAINT uq_windup_project_user_name UNIQUE (user_id, project_name)。
合并 main 时丢掉了 1024XEngineer#76 刚加入的两个 router 注册,合并后 /workflow-runs 与
/ai/chat 由可用变 404,而两个 api 文件仍在仓库里,静默失效无报错。

CORS 两处:去掉写死的 vercel 通配正则,并把 allow_credentials 置 False
(鉴权走 Authorization 头不走 cookie);默认来源补 4173(vite preview)。

实测:端点数 16 → 19,恢复 /ai/chat 与 /workflow-runs{,/{run_id}};
4173 过预检且响应无 allow-credentials 头,任意 vercel 域不再放行。

Refs 1024XEngineer#143

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(app): 补回 workflow-run / ai 两个 router,收紧 CORS(Refs 1024XEngineer#143
@johnnyzhang-eng
johnnyzhang-eng dismissed their stale review August 6, 2026 09:40

不阻塞合并。两条已由 xiaocheny214#35 修复(Refs #143),其余四条记在 #145 备查。

@xiaocheny214 xiaocheny214 changed the title feat: backend dev feat(backend): 后端全栈实现——基础设施 + 领域模块 + 用户认证 + API 契约 Aug 6, 2026

@johnnyzhang-eng johnnyzhang-eng left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve。

合并顺序提醒:xiaocheny214#35 若未先合进 feat/backend-dev,本 PR 会把 #76 刚加入 main 的 workflow_run / ai 两个 router 注册删掉,/workflow-runs/ai/chat 变 404。那个 PR 只改 1 个文件、门禁全绿。

其余四条记在 #145,不阻塞。

@xiaocheny214

Copy link
Copy Markdown
Contributor Author

⚠️ 本 PR 已拆分为 4 个小 PR,便于 review:

PR 分支 内容
#148 infra/deploy Docker Compose + Redis + init.sql
#149 feat/user-auth 用户认证模块
#150 feat/domain-modules 项目/角色/工作流/Agent
#151 feat/generation 生成编排 + 视频下载重试

依赖链:#148#149#150#151

本 PR 将在上述 PR 全部合并后关闭。

@xiaocheny214

Copy link
Copy Markdown
Contributor Author

更新:补充了遗漏的 ai_engine 模块,现在共 5 个小 PR:

PR 分支 内容 主要作者
#148 infra/deploy Docker Compose + Redis + init.sql xiaocheny214
#149 feat/user-auth 用户认证模块 xiaocheny214
#150 feat/domain-modules 项目/角色/工作流/Agent CyberSeeker_Sea
#151 feat/generation 生成编排 + 视频下载重试 johnnyzhang-eng
#152 feat/ai-engine AI 生成引擎管线 johnnyzhang-eng

依赖链:#148#149#150#151#152

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment