From c042937bd214eb18f747b543fc52913acc4ec4fe Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:09:57 +0800 Subject: [PATCH 01/16] feat(docker): add Docker Compose deployment for backend and PostgreSQL - Add backend/Dockerfile with multi-stage build (uv + Python 3.12) - Add docker-compose.yml with backend and PostgreSQL services - Add db/init.sql for automatic database table initialization - Add .env.example with configuration template - PostgreSQL configured with port 7856 and secure password --- backend/Dockerfile | 50 ++++++++++++++++++ db/init.sql | 126 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 77 +++++++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 backend/Dockerfile create mode 100644 db/init.sql create mode 100644 docker-compose.yml diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..fdd78678 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,50 @@ +# ── 后端 Dockerfile ────────────────────────────────────────────────── +# 多阶段构建:builder 装依赖 → runtime 只拷贝产物,镜像更小 + +# ── 阶段 1: 构建 ── +FROM python:3.12-slim AS builder + +# 安装 uv(比 pip 快 10x) +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +WORKDIR /build + +# 先拷贝依赖定义,利用 Docker layer cache +COPY pyproject.toml uv.lock ./ +COPY packages/common/pyproject.toml packages/common/ +COPY packages/framework/pyproject.toml packages/framework/ +COPY packages/ai_engine/pyproject.toml packages/ai_engine/ +COPY packages/app/pyproject.toml packages/app/ + +# 安装依赖(不含 dev 依赖) +RUN uv sync --frozen --no-dev --no-install-workspace + +# 拷贝源码并安装 +COPY packages/ packages/ +RUN uv sync --frozen --no-dev + +# ── 阶段 2: 运行时 ── +FROM python:3.12-slim AS runtime + +WORKDIR /app + +# 从 builder 拷贝虚拟环境和包 +COPY --from=builder /build/.venv /app/.venv +COPY --from=builder /build/packages /app/packages + +# 把 venv/bin 加入 PATH +ENV PATH="/app/.venv/bin:$PATH" + +# 默认环境变量(可被 docker-compose / .env 覆盖) +ENV WINDUP_HOST=0.0.0.0 +ENV WINDUP_PORT=8000 +ENV WINDUP_RELOAD=false + +EXPOSE 8000 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/docs')" || exit 1 + +# 启动命令 +CMD ["uvicorn", "windup_app.bootstrap.app:create_app", "--factory", "--host", "0.0.0.0", "--port", "8000"] diff --git a/db/init.sql b/db/init.sql new file mode 100644 index 00000000..3ab2137d --- /dev/null +++ b/db/init.sql @@ -0,0 +1,126 @@ +-- ────────────────────────────────────────────────────────────── +-- Windup 数据库初始化脚本 +-- 首次启动时自动执行,创建所有表结构 +-- 数据库已通过 POSTGRES_DB 环境变量自动创建 +-- ────────────────────────────────────────────────────────────── + +/*项目表,全局约束角色人物。*/ +CREATE TABLE windup_project ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id BIGINT NOT NULL, + workflow_id BIGINT, + project_name varchar(20) NOT NULL, + character_perspective SMALLINT NOT NULL, + directional_movement SMALLINT NOT NULL, + sprite_width SMALLINT NOT NULL, + sprite_height SMALLINT NOT NULL, + game_style TEXT, + sprite_sample_url TEXT, + create_at TIMESTAMPTZ, + update_at TIMESTAMPTZ +); + +COMMENT ON TABLE windup_project IS '项目表'; +COMMENT ON COLUMN windup_project.id IS '项目表主键'; +COMMENT ON COLUMN windup_project.user_id IS '创建者 ID'; +COMMENT ON COLUMN windup_project.workflow_id IS '工作流 ID'; +COMMENT ON COLUMN windup_project.project_name IS '项目名称'; +COMMENT ON COLUMN windup_project.character_perspective IS '游戏视角 :1 = 横版视角,2 = 俯视 ,3 = 2.5D '; +COMMENT ON COLUMN windup_project.directional_movement IS '移动方向 :1 = 单向 ,2 = 四向,3 = 八向'; +COMMENT ON COLUMN windup_project.sprite_width IS '角色尺寸 宽:32 、64 、128、256、512、1024、2048'; +COMMENT ON COLUMN windup_project.sprite_height IS '角色尺寸 高:32 、64 、128、256、512、1024、2048'; +COMMENT ON COLUMN windup_project.game_style IS '游戏风格'; +COMMENT ON COLUMN windup_project.sprite_sample_url IS '参考图URL'; +COMMENT ON COLUMN windup_project.create_at IS '创建时间'; +COMMENT ON COLUMN windup_project.update_at IS '修改时间'; + +/*用户表*/ +CREATE TABLE windup_user ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + email VARCHAR(255) UNIQUE, + password_hash VARCHAR(255) NOT NULL, + nickname VARCHAR(255), + email_verified_at TIMESTAMPTZ, + status SMALLINT DEFAULT 0, + last_login_at TIMESTAMPTZ, + create_at TIMESTAMPTZ, + update_at TIMESTAMPTZ +); + +COMMENT ON TABLE windup_user IS '用户表'; +COMMENT ON COLUMN windup_user.id IS '用户表主键'; +COMMENT ON COLUMN windup_user.email IS '邮箱地址'; +COMMENT ON COLUMN windup_user.password_hash IS '用户密码(加密存储)'; +COMMENT ON COLUMN windup_user.nickname IS '用户昵称'; +COMMENT ON COLUMN windup_user.email_verified_at IS '邮箱校验时间'; +COMMENT ON COLUMN windup_user.status IS '用户状态 默认0 正常,1 封禁'; +COMMENT ON COLUMN windup_user.last_login_at IS '上次登录时间'; +COMMENT ON COLUMN windup_user.create_at IS '创建时间'; +COMMENT ON COLUMN windup_user.update_at IS '修改时间'; + +/*角色资产表*/ +CREATE TABLE windup_character ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + project_id BIGINT NOT NULL, + name varchar(20), + description TEXT NULL, + reference_image_url TEXT NULL, + character_data JSONB NOT NULL DEFAULT '{}'::jsonb, + status SMALLINT NOT NULL DEFAULT 1, + create_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT ck_windup_character_status CHECK (status IN (0, 1)), + CONSTRAINT ck_windup_character_data_object CHECK (jsonb_typeof(character_data) = 'object') +); + +CREATE INDEX idx_windup_character_project_id + ON windup_character (project_id); + +CREATE INDEX idx_windup_character_project_update_at + ON windup_character (project_id, update_at DESC); + +COMMENT ON TABLE windup_character IS '角色资产表;角色隶属于项目,是资产库中的基本资产'; +COMMENT ON COLUMN windup_character.id IS '角色资产主键'; +COMMENT ON COLUMN windup_character.project_id IS '所属项目 ID'; +COMMENT ON COLUMN windup_character.name IS '角色名称'; +COMMENT ON COLUMN windup_character.description IS '角色描述'; +COMMENT ON COLUMN windup_character.reference_image_url IS '角色参考图 URL;角色模板仅作为角色的参考图属性,不单独建表'; +COMMENT ON COLUMN windup_character.character_data IS '角色完整数据 JSON;包含造型、动作及动作帧等信息'; +COMMENT ON COLUMN windup_character.status IS '角色状态: 1-正常, 0-禁用'; +COMMENT ON COLUMN windup_character.create_at IS '创建时间'; +COMMENT ON COLUMN windup_character.update_at IS '最后更新时间'; +COMMENT ON CONSTRAINT ck_windup_character_status ON windup_character IS '角色状态只能为 0 或 1'; +COMMENT ON CONSTRAINT ck_windup_character_data_object ON windup_character IS '角色完整数据必须为 JSON 对象'; + +/*生成任务表*/ +CREATE TABLE IF NOT EXISTS windup_generation_task ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + project_id BIGINT, + task_type TEXT NOT NULL DEFAULT 'character_image', + status TEXT NOT NULL DEFAULT 'pending', + input_payload JSONB NOT NULL DEFAULT '{}', + result_type TEXT, + result JSONB, + error_message TEXT, + create_at TIMESTAMPTZ NOT NULL DEFAULT now(), + update_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +COMMENT ON TABLE windup_generation_task IS '生成任务表——记录每次AI生成任务'; +COMMENT ON COLUMN windup_generation_task.id IS '任务主键'; +COMMENT ON COLUMN windup_generation_task.user_id IS '发起用户 ID'; +COMMENT ON COLUMN windup_generation_task.project_id IS '关联项目 ID'; +COMMENT ON COLUMN windup_generation_task.task_type IS '任务类型:character_image = 角色图片生成'; +COMMENT ON COLUMN windup_generation_task.status IS '任务状态:pending = 待执行,running = 执行中,completed = 完成,failed = 失败'; +COMMENT ON COLUMN windup_generation_task.input_payload IS '任务输入参数 JSON'; +COMMENT ON COLUMN windup_generation_task.result_type IS '结果类型'; +COMMENT ON COLUMN windup_generation_task.result IS '任务结果 JSON'; +COMMENT ON COLUMN windup_generation_task.error_message IS '错误信息(失败时记录)'; +COMMENT ON COLUMN windup_generation_task.create_at IS '创建时间'; +COMMENT ON COLUMN windup_generation_task.update_at IS '修改时间'; + +CREATE INDEX IF NOT EXISTS idx_generation_task_user_id ON windup_generation_task (user_id); +CREATE INDEX IF NOT EXISTS idx_generation_task_project_id ON windup_generation_task (project_id); +CREATE INDEX IF NOT EXISTS idx_generation_task_status ON windup_generation_task (status); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..1e3ec3c8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,77 @@ +# ── Windup Docker Compose ───────────────────────────────────────────── +# 使用方式: +# 启动服务: docker compose up -d +# 查看日志: docker compose logs -f [service] +# 停止服务: docker compose down +# 清理数据: docker compose down -v (⚠️ 会删除数据库数据) +# 重新构建: docker compose up -d --build + +services: + # ── PostgreSQL 数据库 ── + postgres: + image: postgres:16-alpine + container_name: windup-postgres + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-root} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-W1ndup@2026!Secure} + POSTGRES_DB: ${POSTGRES_DB:-windup} + ports: + - "${POSTGRES_EXTERNAL_PORT:-7856}:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-root} -d ${POSTGRES_DB:-windup}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - windup-net + + # ── 后端 API 服务 ── + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: windup-backend + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + environment: + # 数据库连接(容器内部通信用 postgres 主机名,端口 5432) + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + POSTGRES_USER: ${POSTGRES_USER:-root} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-admin123} + POSTGRES_DB: ${POSTGRES_DB:-windup} + # LLM 配置 + LLM_API_KEY: ${LLM_API_KEY} + LLM_MODEL_ID: ${LLM_MODEL_ID:-doubao-seed-2.0-mini} + LLM_BASE_URL: ${LLM_BASE_URL:-https://api.qnaigc.com/v1} + LLM_IMAGE_MODEL_ID: ${LLM_IMAGE_MODEL_ID:-gemini-3.0-pro-image-preview} + LLM_VIDEO_MODEL_ID: ${LLM_VIDEO_MODEL_ID} + # 搜索 + SERPAPI_API_KEY: ${SERPAPI_API_KEY} + # 七牛云存储 + QINIU_ACCESS_KEY: ${QINIU_ACCESS_KEY} + QINIU_SECRET_KEY: ${QINIU_SECRET_KEY} + QINIU_BUCKET_NAME: ${QINIU_BUCKET_NAME} + QINIU_BUCKET_DOMAIN: ${QINIU_BUCKET_DOMAIN} + QINIU_PRIVATE_SPACE: ${QINIU_PRIVATE_SPACE:-false} + # AI Provider + AI_BASE_URL: ${AI_BASE_URL:-https://api.qnaigc.com/v1} + AI_API_KEY: ${AI_API_KEY} + ports: + - "${WINDUP_PORT:-8000}:8000" + networks: + - windup-net + +volumes: + postgres_data: + driver: local + +networks: + windup-net: + driver: bridge From dc3c362a3a3a0d007459ce115577fc53438c3241 Mon Sep 17 00:00:00 2001 From: Johnny Zhang Date: Tue, 4 Aug 2026 23:08:58 +0800 Subject: [PATCH 02/16] fix(deploy): make the container build and the API reachable from the browser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三处让部署跑不起来的问题,都在这台服务器上实测定位: 1. 构建阶段 uv sync 超时。宿主机访问 pypi.org 需 8s,构建容器内默认超时会在 下载大包(uvloop)时 "operation timed out" 直接失败。改走国内镜像源并把 UV_HTTP_TIMEOUT 拉到 180s。 2. 容器起来即反复重启,报 "exec /app/.venv/bin/uvicorn: no such file or directory"。 文件其实存在,报的是它 shebang 指向的解释器——uv 装出来的 venv 里 shebang 与 .pth 都是绝对路径,builder 在 /build、runtime 在 /app,跨路径拷贝后解释器与 workspace 包全部失效。把 builder 的 WORKDIR 也改成 /app 即可。 3. 七牛上传 TLS 握手超时、媒体上传请求挂死。宿主机网卡 MTU 1480,而 compose 自建网络不继承 daemon 的 mtu 设置、默认仍是 1500,大包被丢。显式给网络设 1450 后,up-z0.qiniup.com 从握手超时 14s 变为 1.0s,上传恢复正常。 4. 浏览器跨域被全部拦下:OPTIONS 预检返回 405、响应无 access-control-* 头, 后端日志里连请求都看不到。挂上 CORSMiddleware,允许来源用 WINDUP_CORS_ORIGINS 覆盖,并放行 Vercel 预览域名。 Co-Authored-By: Claude Opus 5 --- backend/Dockerfile | 13 ++- .../app/src/windup_app/bootstrap/app.py | 84 ++++++++++++++++++- docker-compose.yml | 4 + 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index fdd78678..4761de95 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,7 +7,14 @@ FROM python:3.12-slim AS builder # 安装 uv(比 pip 快 10x) COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv -WORKDIR /build +# ── 国内网络:走镜像源 + 拉长超时 ──────────────────────────────────────── +# 实测(2026-08-04,这台服务器):宿主机访问 pypi.org 需 8s,构建容器内默认超时 +# 会在下载大包(uvloop)时 "operation timed out" 直接失败。 +ENV UV_DEFAULT_INDEX=https://mirrors.aliyun.com/pypi/simple/ \ + UV_HTTP_TIMEOUT=180 + +# 必须与 runtime 阶段同路径 —— venv 内的 shebang/.pth 是绝对路径,跨路径拷贝会失效 +WORKDIR /app # 先拷贝依赖定义,利用 Docker layer cache COPY pyproject.toml uv.lock ./ @@ -29,8 +36,8 @@ FROM python:3.12-slim AS runtime WORKDIR /app # 从 builder 拷贝虚拟环境和包 -COPY --from=builder /build/.venv /app/.venv -COPY --from=builder /build/packages /app/packages +COPY --from=builder /app/.venv /app/.venv +COPY --from=builder /app/packages /app/packages # 把 venv/bin 加入 PATH ENV PATH="/app/.venv/bin:$PATH" diff --git a/backend/packages/app/src/windup_app/bootstrap/app.py b/backend/packages/app/src/windup_app/bootstrap/app.py index 89f7b43d..4c7f484f 100644 --- a/backend/packages/app/src/windup_app/bootstrap/app.py +++ b/backend/packages/app/src/windup_app/bootstrap/app.py @@ -1,15 +1,97 @@ + """FastAPI 应用工厂与装配入口。 ``create_app`` 负责创建 FastAPI 实例并挂载路由 / 中间件 / 异常处理, 是整个 web 服务的唯一装配点(composition root)。 + +``main`` 是开发启动入口:``python -m windup_app`` 或 ``windup`` 命令。 """ +import os +from contextlib import asynccontextmanager + +import windup_framework.db # noqa: F401 组装时显式触发 DB engine/session 初始化 from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from windup_app.server.orchestrator.executor import run_action_task, run_image_task +from windup_app.web.api.character import router as character_router +from windup_app.web.api.generation import router as generation_router from windup_app.web.api.media import router as media_router +from windup_app.web.api.project import router as project_router +from windup_app.web.handler.exception_handlers import register_exception_handlers + + +def _env_flag(name: str) -> bool: + """把环境变量解析为真正的布尔值:仅 1/true/yes/on(忽略大小写与空白)视为 True。""" + return os.getenv(name, "").strip().lower() in {"1", "true", "yes", "on"} + + + +def _cors_origins() -> list[str]: + """允许跨域的前端来源,逗号分隔的 WINDUP_CORS_ORIGINS 覆盖。 + + 不配这个中间件的话,浏览器会把前端的**所有**请求拦在预检那一步 + (OPTIONS 返回 405、响应无 access-control-* 头),后端日志里连请求都看不到。 + 默认值覆盖本地 dev server 与 Vercel 预览域名。 + """ + raw = os.getenv("WINDUP_CORS_ORIGINS", "").strip() + if raw: + return [o.strip() for o in raw.split(",") if o.strip()] + return ["http://localhost:5173", "http://127.0.0.1:5173", + "http://localhost:3000", "http://127.0.0.1:3000"] + + +def print_banner() -> None: + """启动时打印 banner(占位实现,后续替换为正式 ASCII banner)。""" + print("windup 0.1.0 starting ...") + + +@asynccontextmanager +async def _lifespan(app: FastAPI): + """应用启动时打印 banner,关闭时无特殊处理。""" + print_banner() + yield def create_app() -> FastAPI: - app = FastAPI(title="windup", version="0.1.0") + app = FastAPI(title="windup", version="0.1.0", lifespan=_lifespan) + app.add_middleware( + CORSMiddleware, + allow_origins=_cors_origins(), + allow_origin_regex=r"https://.*\.vercel\.app", + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + app.include_router(project_router) + app.include_router(character_router) app.include_router(media_router) + app.include_router(generation_router) + # 生成后台调度器注入 app.state:bootstrap(composition root)持有 ai_engine 依赖, + # web 端运行期从 request.app.state 取,避免 web 静态 import ai_engine(入口层门禁)。 + app.state.run_action_task = run_action_task + app.state.run_image_task = run_image_task + register_exception_handlers(app) return app + + +def main() -> None: + """开发启动入口:用 uvicorn 跑 ``create_app``。 + + host/port/reload 可用 ``WINDUP_HOST`` / ``WINDUP_PORT`` / ``WINDUP_RELOAD`` 覆盖。 + """ + import uvicorn + + uvicorn.run( + "windup_app.bootstrap.app:create_app", + factory=True, + host=os.getenv("WINDUP_HOST", "127.0.0.1"), + port=int(os.getenv("WINDUP_PORT", "8000")), + reload=_env_flag("WINDUP_RELOAD"), + ) + + + +if __name__ == "__main__": + main() diff --git a/docker-compose.yml b/docker-compose.yml index 1e3ec3c8..94f1ac14 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -75,3 +75,7 @@ volumes: networks: windup-net: driver: bridge + # 宿主机链路 MTU 是 1480(eno1),compose 自建网络不会继承 daemon 的 mtu 设置, + # 默认仍是 1500 → 大包被丢,表现为 TLS 握手超时(七牛上传域名连不上、pip 下载卡死)。 + driver_opts: + com.docker.network.driver.mtu: "1450" From 0fb5bf0d4738bec8b9867474170296ad48564cfa Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:20:27 +0800 Subject: [PATCH 03/16] =?UTF-8?q?fix(deps):=20docker-compose=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20Redis=20=E6=9C=8D=E5=8A=A1=EF=BC=8C=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=20REDIS=5FURL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端验证码/refresh_token 依赖 Redis,原 compose 只有 Postgres。 新增 redis:7-alpine 服务(含健康检查),backend depends_on 等待就绪, 环境变量 REDIS_URL=redis://redis:6379/0。 --- docker-compose.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 94f1ac14..ab4a7ae6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,19 @@ # 重新构建: docker compose up -d --build services: + # ── Redis 缓存(验证码 / refresh_token) ── + redis: + image: redis:7-alpine + container_name: windup-redis + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - windup-net + # ── PostgreSQL 数据库 ── postgres: image: postgres:16-alpine @@ -39,7 +52,11 @@ services: depends_on: postgres: condition: service_healthy + redis: + condition: service_healthy environment: + # Redis(验证码 / refresh_token) + REDIS_URL: redis://redis:6379/0 # 数据库连接(容器内部通信用 postgres 主机名,端口 5432) POSTGRES_HOST: postgres POSTGRES_PORT: 5432 From f99f30bce02c9015cdd79fd84173de22337cae5d Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:20:40 +0800 Subject: [PATCH 04/16] =?UTF-8?q?fix(db):=20init.sql=20=E8=A1=A5=E5=85=85?= =?UTF-8?q?=20windup=5Fproject=20=E5=94=AF=E4=B8=80=E7=BA=A6=E6=9D=9F?= =?UTF-8?q?=EF=BC=8C=E5=AF=B9=E9=BD=90=20ORM=20=E5=A3=B0=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ORM 声明了 UniqueConstraint(user_id, project_name),但 init.sql 建表时遗漏。 生产 Postgres 并发创建同名项目不会触发 IntegrityError,API 兜底失效。 补上 CONSTRAINT uq_windup_project_user_name UNIQUE (user_id, project_name)。 --- db/init.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/db/init.sql b/db/init.sql index 3ab2137d..cee115ee 100644 --- a/db/init.sql +++ b/db/init.sql @@ -17,7 +17,9 @@ CREATE TABLE windup_project ( game_style TEXT, sprite_sample_url TEXT, create_at TIMESTAMPTZ, - update_at TIMESTAMPTZ + update_at TIMESTAMPTZ, + + CONSTRAINT uq_windup_project_user_name UNIQUE (user_id, project_name) ); COMMENT ON TABLE windup_project IS '项目表'; From 746152c78fe62bc2938cf3aca47ed42b563bc99d Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:07:26 +0800 Subject: [PATCH 05/16] feat(db): add workflow_run table and character.workflow_run_id FK --- db/init.sql | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/db/init.sql b/db/init.sql index cee115ee..a84847c5 100644 --- a/db/init.sql +++ b/db/init.sql @@ -64,6 +64,7 @@ COMMENT ON COLUMN windup_user.update_at IS '修改时间'; CREATE TABLE windup_character ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, project_id BIGINT NOT NULL, + workflow_run_id BIGINT, name varchar(20), description TEXT NULL, reference_image_url TEXT NULL, @@ -85,6 +86,7 @@ CREATE INDEX idx_windup_character_project_update_at COMMENT ON TABLE windup_character IS '角色资产表;角色隶属于项目,是资产库中的基本资产'; COMMENT ON COLUMN windup_character.id IS '角色资产主键'; COMMENT ON COLUMN windup_character.project_id IS '所属项目 ID'; +COMMENT ON COLUMN windup_character.workflow_run_id IS '工作流执行记录表主键 ID'; COMMENT ON COLUMN windup_character.name IS '角色名称'; COMMENT ON COLUMN windup_character.description IS '角色描述'; COMMENT ON COLUMN windup_character.reference_image_url IS '角色参考图 URL;角色模板仅作为角色的参考图属性,不单独建表'; @@ -126,3 +128,35 @@ COMMENT ON COLUMN windup_generation_task.update_at IS '修改时间'; CREATE INDEX IF NOT EXISTS idx_generation_task_user_id ON windup_generation_task (user_id); CREATE INDEX IF NOT EXISTS idx_generation_task_project_id ON windup_generation_task (project_id); CREATE INDEX IF NOT EXISTS idx_generation_task_status ON windup_generation_task (status); + +/*工作流执行记录表*/ +CREATE TABLE windup_workflow_run ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + project_id BIGINT NOT NULL, + nodes JSONB NOT NULL DEFAULT '[]', + status VARCHAR(20) NOT NULL DEFAULT 'active', + version INTEGER NOT NULL DEFAULT 1, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + + CONSTRAINT fk_run_project FOREIGN KEY (project_id) REFERENCES windup_project(id) +); + +COMMENT ON TABLE windup_workflow_run IS '工作流执行记录——前端维护的节点树的持久化容器'; +COMMENT ON COLUMN windup_workflow_run.id IS '主键'; +COMMENT ON COLUMN windup_workflow_run.project_id IS '关联项目 ID'; +COMMENT ON COLUMN windup_workflow_run.nodes IS '节点树 JSONB(前端自定义结构,后端不校验)'; +COMMENT ON COLUMN windup_workflow_run.status IS '状态:active=进行中,soft_deleted=已软删除'; +COMMENT ON COLUMN windup_workflow_run.version IS '版本号,从 1 递增'; +COMMENT ON COLUMN windup_workflow_run.created_at IS '创建时间'; + +-- 索引 +CREATE INDEX idx_run_project ON windup_workflow_run(project_id); + + +ALTER TABLE windup_character + ADD CONSTRAINT fk_character_workflow_run + FOREIGN KEY (workflow_run_id) REFERENCES windup_workflow_run(id); + +COMMENT ON COLUMN windup_character.workflow_run_id IS '关联的工作流执行记录 ID(用于回退节点重新生成)'; + +CREATE INDEX idx_character_workflow_run ON windup_character(workflow_run_id) WHERE workflow_run_id IS NOT NULL; From f4969f210c24b4454577981f3887a11442c61492 Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:10:24 +0800 Subject: [PATCH 06/16] fix(app): remove orchestrator import, not needed for deploy --- backend/packages/app/src/windup_app/bootstrap/app.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend/packages/app/src/windup_app/bootstrap/app.py b/backend/packages/app/src/windup_app/bootstrap/app.py index 4c7f484f..a72d1b66 100644 --- a/backend/packages/app/src/windup_app/bootstrap/app.py +++ b/backend/packages/app/src/windup_app/bootstrap/app.py @@ -14,7 +14,6 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from windup_app.server.orchestrator.executor import run_action_task, run_image_task from windup_app.web.api.character import router as character_router from windup_app.web.api.generation import router as generation_router from windup_app.web.api.media import router as media_router @@ -68,10 +67,6 @@ def create_app() -> FastAPI: app.include_router(character_router) app.include_router(media_router) app.include_router(generation_router) - # 生成后台调度器注入 app.state:bootstrap(composition root)持有 ai_engine 依赖, - # web 端运行期从 request.app.state 取,避免 web 静态 import ai_engine(入口层门禁)。 - app.state.run_action_task = run_action_task - app.state.run_image_task = run_image_task register_exception_handlers(app) return app From 8905066bc25ace292071148df0600153ea751f7a Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:13:21 +0800 Subject: [PATCH 07/16] fix(app): remove character/project router imports, not present on deploy branch --- backend/packages/app/src/windup_app/bootstrap/app.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/packages/app/src/windup_app/bootstrap/app.py b/backend/packages/app/src/windup_app/bootstrap/app.py index a72d1b66..c2ce4054 100644 --- a/backend/packages/app/src/windup_app/bootstrap/app.py +++ b/backend/packages/app/src/windup_app/bootstrap/app.py @@ -14,10 +14,8 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from windup_app.web.api.character import router as character_router from windup_app.web.api.generation import router as generation_router from windup_app.web.api.media import router as media_router -from windup_app.web.api.project import router as project_router from windup_app.web.handler.exception_handlers import register_exception_handlers @@ -63,8 +61,6 @@ def create_app() -> FastAPI: allow_methods=["*"], allow_headers=["*"], ) - app.include_router(project_router) - app.include_router(character_router) app.include_router(media_router) app.include_router(generation_router) register_exception_handlers(app) From 80291706ecdcf8042a51fa146fdce1090524912c Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:17:12 +0800 Subject: [PATCH 08/16] fix(deploy): align backend POSTGRES_PASSWORD default with postgres service --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ab4a7ae6..38725e33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,7 +61,7 @@ services: POSTGRES_HOST: postgres POSTGRES_PORT: 5432 POSTGRES_USER: ${POSTGRES_USER:-root} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-admin123} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-W1ndup@2026!Secure} POSTGRES_DB: ${POSTGRES_DB:-windup} # LLM 配置 LLM_API_KEY: ${LLM_API_KEY} From 04d226c26824216d690c9ce0605f141e4b2de729 Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:57:34 +0800 Subject: [PATCH 09/16] chore(deploy): remove init.sql mount, schema now managed by ORM --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 38725e33..7df5ccf1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,7 +33,6 @@ services: - "${POSTGRES_EXTERNAL_PORT:-7856}:5432" volumes: - postgres_data:/var/lib/postgresql/data - - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-root} -d ${POSTGRES_DB:-windup}"] interval: 10s From 251d0c2c430fa098230d982d056ee846d5e39ca7 Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:01:09 +0800 Subject: [PATCH 10/16] chore(deploy): mount postgres data to host dir, add data/ to gitignore --- .gitignore | 3 +++ docker-compose.yml | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 39bf5e20..072c5a7b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ venv/ .env .env.* +# Docker 数据 +data/ + # 运行产物 output/ diff --git a/docker-compose.yml b/docker-compose.yml index 7df5ccf1..99a113b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: ports: - "${POSTGRES_EXTERNAL_PORT:-7856}:5432" volumes: - - postgres_data:/var/lib/postgresql/data + - ${POSTGRES_DATA_DIR:-./data/postgres}:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-root} -d ${POSTGRES_DB:-windup}"] interval: 10s @@ -84,10 +84,6 @@ services: networks: - windup-net -volumes: - postgres_data: - driver: local - networks: windup-net: driver: bridge From f17f4c54db1373bd88bdfc656f6b356365187f94 Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:02:09 +0800 Subject: [PATCH 11/16] chore(deploy): mount redis data to host dir for persistence --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 99a113b2..16797ae9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ services: image: redis:7-alpine container_name: windup-redis restart: unless-stopped + volumes: + - ${REDIS_DATA_DIR:-./data/redis}:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s From 86c3ca036456df445145bbaf5c4b715c8caf6aaa Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:03:45 +0800 Subject: [PATCH 12/16] chore(db): remove init.sql, schema now managed by ORM --- db/init.sql | 162 ---------------------------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 db/init.sql diff --git a/db/init.sql b/db/init.sql deleted file mode 100644 index a84847c5..00000000 --- a/db/init.sql +++ /dev/null @@ -1,162 +0,0 @@ --- ────────────────────────────────────────────────────────────── --- Windup 数据库初始化脚本 --- 首次启动时自动执行,创建所有表结构 --- 数据库已通过 POSTGRES_DB 环境变量自动创建 --- ────────────────────────────────────────────────────────────── - -/*项目表,全局约束角色人物。*/ -CREATE TABLE windup_project ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - user_id BIGINT NOT NULL, - workflow_id BIGINT, - project_name varchar(20) NOT NULL, - character_perspective SMALLINT NOT NULL, - directional_movement SMALLINT NOT NULL, - sprite_width SMALLINT NOT NULL, - sprite_height SMALLINT NOT NULL, - game_style TEXT, - sprite_sample_url TEXT, - create_at TIMESTAMPTZ, - update_at TIMESTAMPTZ, - - CONSTRAINT uq_windup_project_user_name UNIQUE (user_id, project_name) -); - -COMMENT ON TABLE windup_project IS '项目表'; -COMMENT ON COLUMN windup_project.id IS '项目表主键'; -COMMENT ON COLUMN windup_project.user_id IS '创建者 ID'; -COMMENT ON COLUMN windup_project.workflow_id IS '工作流 ID'; -COMMENT ON COLUMN windup_project.project_name IS '项目名称'; -COMMENT ON COLUMN windup_project.character_perspective IS '游戏视角 :1 = 横版视角,2 = 俯视 ,3 = 2.5D '; -COMMENT ON COLUMN windup_project.directional_movement IS '移动方向 :1 = 单向 ,2 = 四向,3 = 八向'; -COMMENT ON COLUMN windup_project.sprite_width IS '角色尺寸 宽:32 、64 、128、256、512、1024、2048'; -COMMENT ON COLUMN windup_project.sprite_height IS '角色尺寸 高:32 、64 、128、256、512、1024、2048'; -COMMENT ON COLUMN windup_project.game_style IS '游戏风格'; -COMMENT ON COLUMN windup_project.sprite_sample_url IS '参考图URL'; -COMMENT ON COLUMN windup_project.create_at IS '创建时间'; -COMMENT ON COLUMN windup_project.update_at IS '修改时间'; - -/*用户表*/ -CREATE TABLE windup_user ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - email VARCHAR(255) UNIQUE, - password_hash VARCHAR(255) NOT NULL, - nickname VARCHAR(255), - email_verified_at TIMESTAMPTZ, - status SMALLINT DEFAULT 0, - last_login_at TIMESTAMPTZ, - create_at TIMESTAMPTZ, - update_at TIMESTAMPTZ -); - -COMMENT ON TABLE windup_user IS '用户表'; -COMMENT ON COLUMN windup_user.id IS '用户表主键'; -COMMENT ON COLUMN windup_user.email IS '邮箱地址'; -COMMENT ON COLUMN windup_user.password_hash IS '用户密码(加密存储)'; -COMMENT ON COLUMN windup_user.nickname IS '用户昵称'; -COMMENT ON COLUMN windup_user.email_verified_at IS '邮箱校验时间'; -COMMENT ON COLUMN windup_user.status IS '用户状态 默认0 正常,1 封禁'; -COMMENT ON COLUMN windup_user.last_login_at IS '上次登录时间'; -COMMENT ON COLUMN windup_user.create_at IS '创建时间'; -COMMENT ON COLUMN windup_user.update_at IS '修改时间'; - -/*角色资产表*/ -CREATE TABLE windup_character ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - project_id BIGINT NOT NULL, - workflow_run_id BIGINT, - name varchar(20), - description TEXT NULL, - reference_image_url TEXT NULL, - character_data JSONB NOT NULL DEFAULT '{}'::jsonb, - status SMALLINT NOT NULL DEFAULT 1, - create_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - update_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT ck_windup_character_status CHECK (status IN (0, 1)), - CONSTRAINT ck_windup_character_data_object CHECK (jsonb_typeof(character_data) = 'object') -); - -CREATE INDEX idx_windup_character_project_id - ON windup_character (project_id); - -CREATE INDEX idx_windup_character_project_update_at - ON windup_character (project_id, update_at DESC); - -COMMENT ON TABLE windup_character IS '角色资产表;角色隶属于项目,是资产库中的基本资产'; -COMMENT ON COLUMN windup_character.id IS '角色资产主键'; -COMMENT ON COLUMN windup_character.project_id IS '所属项目 ID'; -COMMENT ON COLUMN windup_character.workflow_run_id IS '工作流执行记录表主键 ID'; -COMMENT ON COLUMN windup_character.name IS '角色名称'; -COMMENT ON COLUMN windup_character.description IS '角色描述'; -COMMENT ON COLUMN windup_character.reference_image_url IS '角色参考图 URL;角色模板仅作为角色的参考图属性,不单独建表'; -COMMENT ON COLUMN windup_character.character_data IS '角色完整数据 JSON;包含造型、动作及动作帧等信息'; -COMMENT ON COLUMN windup_character.status IS '角色状态: 1-正常, 0-禁用'; -COMMENT ON COLUMN windup_character.create_at IS '创建时间'; -COMMENT ON COLUMN windup_character.update_at IS '最后更新时间'; -COMMENT ON CONSTRAINT ck_windup_character_status ON windup_character IS '角色状态只能为 0 或 1'; -COMMENT ON CONSTRAINT ck_windup_character_data_object ON windup_character IS '角色完整数据必须为 JSON 对象'; - -/*生成任务表*/ -CREATE TABLE IF NOT EXISTS windup_generation_task ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL, - project_id BIGINT, - task_type TEXT NOT NULL DEFAULT 'character_image', - status TEXT NOT NULL DEFAULT 'pending', - input_payload JSONB NOT NULL DEFAULT '{}', - result_type TEXT, - result JSONB, - error_message TEXT, - create_at TIMESTAMPTZ NOT NULL DEFAULT now(), - update_at TIMESTAMPTZ NOT NULL DEFAULT now() -); - -COMMENT ON TABLE windup_generation_task IS '生成任务表——记录每次AI生成任务'; -COMMENT ON COLUMN windup_generation_task.id IS '任务主键'; -COMMENT ON COLUMN windup_generation_task.user_id IS '发起用户 ID'; -COMMENT ON COLUMN windup_generation_task.project_id IS '关联项目 ID'; -COMMENT ON COLUMN windup_generation_task.task_type IS '任务类型:character_image = 角色图片生成'; -COMMENT ON COLUMN windup_generation_task.status IS '任务状态:pending = 待执行,running = 执行中,completed = 完成,failed = 失败'; -COMMENT ON COLUMN windup_generation_task.input_payload IS '任务输入参数 JSON'; -COMMENT ON COLUMN windup_generation_task.result_type IS '结果类型'; -COMMENT ON COLUMN windup_generation_task.result IS '任务结果 JSON'; -COMMENT ON COLUMN windup_generation_task.error_message IS '错误信息(失败时记录)'; -COMMENT ON COLUMN windup_generation_task.create_at IS '创建时间'; -COMMENT ON COLUMN windup_generation_task.update_at IS '修改时间'; - -CREATE INDEX IF NOT EXISTS idx_generation_task_user_id ON windup_generation_task (user_id); -CREATE INDEX IF NOT EXISTS idx_generation_task_project_id ON windup_generation_task (project_id); -CREATE INDEX IF NOT EXISTS idx_generation_task_status ON windup_generation_task (status); - -/*工作流执行记录表*/ -CREATE TABLE windup_workflow_run ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - project_id BIGINT NOT NULL, - nodes JSONB NOT NULL DEFAULT '[]', - status VARCHAR(20) NOT NULL DEFAULT 'active', - version INTEGER NOT NULL DEFAULT 1, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - - CONSTRAINT fk_run_project FOREIGN KEY (project_id) REFERENCES windup_project(id) -); - -COMMENT ON TABLE windup_workflow_run IS '工作流执行记录——前端维护的节点树的持久化容器'; -COMMENT ON COLUMN windup_workflow_run.id IS '主键'; -COMMENT ON COLUMN windup_workflow_run.project_id IS '关联项目 ID'; -COMMENT ON COLUMN windup_workflow_run.nodes IS '节点树 JSONB(前端自定义结构,后端不校验)'; -COMMENT ON COLUMN windup_workflow_run.status IS '状态:active=进行中,soft_deleted=已软删除'; -COMMENT ON COLUMN windup_workflow_run.version IS '版本号,从 1 递增'; -COMMENT ON COLUMN windup_workflow_run.created_at IS '创建时间'; - --- 索引 -CREATE INDEX idx_run_project ON windup_workflow_run(project_id); - - -ALTER TABLE windup_character - ADD CONSTRAINT fk_character_workflow_run - FOREIGN KEY (workflow_run_id) REFERENCES windup_workflow_run(id); - -COMMENT ON COLUMN windup_character.workflow_run_id IS '关联的工作流执行记录 ID(用于回退节点重新生成)'; - -CREATE INDEX idx_character_workflow_run ON windup_character(workflow_run_id) WHERE workflow_run_id IS NOT NULL; From de24b296b2b5439282828cd4aa30cbeda8b321a3 Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:19:07 +0800 Subject: [PATCH 13/16] refactor(deploy): use env_file to load .env, only override Docker-specific vars --- docker-compose.yml | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 16797ae9..b1eaf551 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,32 +55,13 @@ services: condition: service_healthy redis: condition: service_healthy + env_file: + - .env environment: - # Redis(验证码 / refresh_token) + # 容器内部通信用 Docker 主机名,覆盖 .env 中的本地开发值 REDIS_URL: redis://redis:6379/0 - # 数据库连接(容器内部通信用 postgres 主机名,端口 5432) POSTGRES_HOST: postgres POSTGRES_PORT: 5432 - POSTGRES_USER: ${POSTGRES_USER:-root} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-W1ndup@2026!Secure} - POSTGRES_DB: ${POSTGRES_DB:-windup} - # LLM 配置 - LLM_API_KEY: ${LLM_API_KEY} - LLM_MODEL_ID: ${LLM_MODEL_ID:-doubao-seed-2.0-mini} - LLM_BASE_URL: ${LLM_BASE_URL:-https://api.qnaigc.com/v1} - LLM_IMAGE_MODEL_ID: ${LLM_IMAGE_MODEL_ID:-gemini-3.0-pro-image-preview} - LLM_VIDEO_MODEL_ID: ${LLM_VIDEO_MODEL_ID} - # 搜索 - SERPAPI_API_KEY: ${SERPAPI_API_KEY} - # 七牛云存储 - QINIU_ACCESS_KEY: ${QINIU_ACCESS_KEY} - QINIU_SECRET_KEY: ${QINIU_SECRET_KEY} - QINIU_BUCKET_NAME: ${QINIU_BUCKET_NAME} - QINIU_BUCKET_DOMAIN: ${QINIU_BUCKET_DOMAIN} - QINIU_PRIVATE_SPACE: ${QINIU_PRIVATE_SPACE:-false} - # AI Provider - AI_BASE_URL: ${AI_BASE_URL:-https://api.qnaigc.com/v1} - AI_API_KEY: ${AI_API_KEY} ports: - "${WINDUP_PORT:-8000}:8000" networks: From 83fd9e4a4302592ff5d49a919ec47959ded486d6 Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:38:22 +0800 Subject: [PATCH 14/16] docs: add POSTGRES_HOST/PORT, REDIS_URL, data dir to .env.example --- .env.example | 44 ++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + 2 files changed, 45 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..c848d32b --- /dev/null +++ b/.env.example @@ -0,0 +1,44 @@ +# ── Windup 环境变量配置示例 ─────────────────────────────────────────── +# 复制此文件为 .env 并填入实际值 +# cp .env.example .env + +# ── PostgreSQL 配置 ── +POSTGRES_USER=root +POSTGRES_PASSWORD=W1ndup@2026!Secure +POSTGRES_DB=windup +POSTGRES_HOST=localhost +POSTGRES_PORT=7856 +# 宿主机访问数据库的端口(Docker 映射用) +POSTGRES_EXTERNAL_PORT=7856 +# 数据持久化目录(默认 ./data/postgres) +POSTGRES_DATA_DIR=./data/postgres + +# ── Redis 配置 ── +REDIS_URL=redis://localhost:6379/0 +# 数据持久化目录(默认 ./data/redis) +REDIS_DATA_DIR=./data/redis + +# ── LLM 配置 ── +LLM_API_KEY=your-api-key-here +LLM_MODEL_ID=doubao-seed-2.0-mini +LLM_BASE_URL=https://api.qnaigc.com/v1 +LLM_IMAGE_MODEL_ID=gemini-3.0-pro-image-preview +LLM_VIDEO_MODEL_ID= + +# ── 搜索 API ── +SERPAPI_API_KEY=your-serpapi-key + +# ── 七牛云存储 ── +QINIU_ACCESS_KEY=your-access-key +QINIU_SECRET_KEY=your-secret-key +QINIU_BUCKET_NAME=your-bucket +QINIU_BUCKET_DOMAIN=your-domain.com +QINIU_PRIVATE_SPACE=false + +# ── AI Provider ── +AI_BASE_URL=https://api.qnaigc.com/v1 +AI_API_KEY=your-ai-api-key + +# ── 服务端口配置 ── +# 后端 API 端口 +WINDUP_PORT=8000 diff --git a/.gitignore b/.gitignore index 072c5a7b..6de0b26d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ venv/ # 敏感配置(切勿提交) .env .env.* +!.env.example # Docker 数据 data/ From abfefbb2866c4b26b82194ef97b85a8d6c04f640 Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:06:03 +0800 Subject: [PATCH 15/16] refactor(app): make CORS origin regex configurable via env var --- .env.example | 8 ++++++-- backend/packages/app/src/windup_app/bootstrap/app.py | 10 +++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index c848d32b..409a5714 100644 --- a/.env.example +++ b/.env.example @@ -39,6 +39,10 @@ QINIU_PRIVATE_SPACE=false AI_BASE_URL=https://api.qnaigc.com/v1 AI_API_KEY=your-ai-api-key -# ── 服务端口配置 ── -# 后端 API 端口 +# ── 服务配置 ── +WINDUP_HOST=127.0.0.1 WINDUP_PORT=8000 +# 跨域来源(逗号分隔,默认覆盖 localhost:5173/3000) +WINDUP_CORS_ORIGINS= +# 跨域正则匹配(默认允许 *.vercel.app) +WINDUP_CORS_ORIGIN_REGEX=https://.*\.vercel\.app diff --git a/backend/packages/app/src/windup_app/bootstrap/app.py b/backend/packages/app/src/windup_app/bootstrap/app.py index c2ce4054..d3ad65eb 100644 --- a/backend/packages/app/src/windup_app/bootstrap/app.py +++ b/backend/packages/app/src/windup_app/bootstrap/app.py @@ -39,6 +39,14 @@ def _cors_origins() -> list[str]: "http://localhost:3000", "http://127.0.0.1:3000"] +def _cors_origin_regex() -> str | None: + """CORS 正则匹配的额外来源,WINDUP_CORS_ORIGIN_REGEX 覆盖。 + + 默认允许所有 Vercel 预览域名。 + """ + return os.getenv("WINDUP_CORS_ORIGIN_REGEX", r"https://.*\.vercel\.app").strip() or None + + def print_banner() -> None: """启动时打印 banner(占位实现,后续替换为正式 ASCII banner)。""" print("windup 0.1.0 starting ...") @@ -56,7 +64,7 @@ def create_app() -> FastAPI: app.add_middleware( CORSMiddleware, allow_origins=_cors_origins(), - allow_origin_regex=r"https://.*\.vercel\.app", + allow_origin_regex=_cors_origin_regex(), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], From 21a3c60cbb071590e669ae719ac25424339bc3a1 Mon Sep 17 00:00:00 2001 From: xiaocheny214 <187097481+xiaocheny214@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:06:59 +0800 Subject: [PATCH 16/16] docs: replace real password with placeholder in .env.example --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 409a5714..77df9d48 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ # ── PostgreSQL 配置 ── POSTGRES_USER=root -POSTGRES_PASSWORD=W1ndup@2026!Secure +POSTGRES_PASSWORD=your-password-here POSTGRES_DB=windup POSTGRES_HOST=localhost POSTGRES_PORT=7856