diff --git a/backend/.env.test b/backend/.env.test index 84c1de5..2ed98a5 100644 --- a/backend/.env.test +++ b/backend/.env.test @@ -2,5 +2,6 @@ DATABASE_URL=sqlite+aiosqlite:///./test.db REDIS_URL=redis://localhost:6379 ENVIRONMENT=testing LOG_LEVEL=info +JWT_SECRET=test-jwt-secret-for-testing-at-least-32-characters-long SECRET_KEY=test-secret-key-for-testing-only CORS_ORIGINS=http://localhost:3000 diff --git a/backend/Dockerfile b/backend/Dockerfile index 3c04f46..11c8b6a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -39,7 +39,7 @@ EXPOSE 8000 # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ - CMD curl -f http://localhost:8000/api/health || exit 1 + CMD curl -f http://localhost:8000/health || exit 1 CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", \ "--bind", "0.0.0.0:8000", "--timeout", "120", "--access-logfile", "-"] diff --git a/backend/app/models/diagnosis_record.py b/backend/app/models/diagnosis_record.py index e83270a..bb0321f 100644 --- a/backend/app/models/diagnosis_record.py +++ b/backend/app/models/diagnosis_record.py @@ -1,7 +1,7 @@ import uuid from datetime import datetime -from sqlalchemy import String, Uuid, JSON, Float, Text, ForeignKey, Index, func +from sqlalchemy import String, Uuid, JSON, Float, Text, DateTime, ForeignKey, Index, func from sqlalchemy.orm import Mapped, mapped_column from app.database import Base @@ -28,9 +28,9 @@ class DiagnosisRecord(Base): error_message: Mapped[str | None] = mapped_column(Text, nullable=True) collection_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column( - server_default=func.now(), nullable=False + DateTime(timezone=True), server_default=func.now(), nullable=False ) - completed_at: Mapped[datetime | None] = mapped_column(nullable=True) + completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) __table_args__ = ( Index("idx_diagnosis_records_brand_id", "brand_id"), diff --git a/backend/app/models/payment_order.py b/backend/app/models/payment_order.py index 22e6905..988e80b 100644 --- a/backend/app/models/payment_order.py +++ b/backend/app/models/payment_order.py @@ -29,12 +29,14 @@ class PaymentOrder(Base): pay_url: Mapped[str | None] = mapped_column(String(1024), nullable=True) callback_data: Mapped[dict | None] = mapped_column(JSONType, nullable=True) created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), server_default=func.now(), nullable=False, ) updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False, ) - paid_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) + paid_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) diff --git a/backend/app/models/subscription.py b/backend/app/models/subscription.py index b83d4cb..9a7906d 100644 --- a/backend/app/models/subscription.py +++ b/backend/app/models/subscription.py @@ -1,7 +1,7 @@ import uuid from datetime import datetime, date -from sqlalchemy import String, ForeignKey, Numeric, func +from sqlalchemy import String, ForeignKey, Numeric, DateTime, func from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -29,6 +29,7 @@ class Subscription(Base): payment_method: Mapped[str | None] = mapped_column(String(50), nullable=True) payment_id: Mapped[str | None] = mapped_column(String(255), nullable=True) created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), server_default=func.now(), nullable=False, ) diff --git a/backend/app/models/user.py b/backend/app/models/user.py index af0ede4..00cce23 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -22,13 +22,13 @@ class User(Base): isActive: Mapped[bool] = mapped_column(Boolean, default=True) emailVerified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) phoneVerified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) - lastLoginAt: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) - createdAt: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False) - updatedAt: Mapped[datetime] = mapped_column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) + lastLoginAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) + createdAt: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) + updatedAt: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=func.now(), onupdate=func.now(), nullable=False) mfaSecret: Mapped[str | None] = mapped_column(Text, nullable=True) mfaEnabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) loginAttempts: Mapped[int] = mapped_column(Integer, default=0, nullable=False) - lockedUntil: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) + lockedUntil: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) organization_id: Mapped[uuid.UUID | None] = mapped_column(Uuid(as_uuid=True), ForeignKey("organizations.id", ondelete="SET NULL"), nullable=True) role: Mapped[str] = mapped_column(String(20), server_default="owner", nullable=False) plan: Mapped[str] = mapped_column(String(20), server_default="free", nullable=False) diff --git a/docker-compose.yml b/docker-compose.yml index ab184f3..7c14848 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,6 +56,9 @@ services: - "8000:8000" env_file: - .env + environment: + DATABASE_URL: postgresql+asyncpg://postgres:postgres123@db:5432/geo_platform + REDIS_URL: redis://redis:6379/0 volumes: - ./backend:/app depends_on: diff --git a/docs/brainstorms/2026-06-01-geo-launch-readiness-requirements.md b/docs/brainstorms/2026-06-01-geo-launch-readiness-requirements.md new file mode 100644 index 0000000..d83c453 --- /dev/null +++ b/docs/brainstorms/2026-06-01-geo-launch-readiness-requirements.md @@ -0,0 +1,109 @@ +--- +date: "2026-06-01" +topic: geo-launch-readiness +--- + +## Summary + +GEO 平台变现闭环代码已全部落地(Plan 003),但从未端到端运行过。本需求定义一个"部署+验证"冲刺:先将系统部署到类生产环境,再在部署环境中验证完整变现闭环(注册→诊断→健康分→付费墙→支付→解锁),直到新用户可完成全链路操作。 + +## Problem Frame + +Plan 003 完成了 9 个实施单元的代码编写——诊断数据采集、免费健康分页面、Onboarding 重设计、支付集成、AI 内容生成、效果归因、邮件集成、契约测试、E2E 烟雾测试。但这些代码从未作为完整系统运行过。后端服务能否启动、前端能否构建、数据库迁移能否执行、前后端联调是否通畅——全部未知。在代码从未跑过的状态下,任何单点修复都是盲目的;只有让系统先跑起来,才能发现真正的阻塞问题。 + +## Key Decisions + +**部署优先于功能验证。** 本地跑通不等于可上线。部署问题(数据库迁移、环境变量、CORS、静态资源)往往是上线前最大的意外。先解决部署,再在部署环境中验证流程,一步到位。 + +**支付用 mock 模式验证。** 真实微信/支付宝 SDK 接入涉及商户审核、证书配置,是独立的长期工作。本次验证用 mock 支付确认付费墙逻辑正确即可,真实支付接入作为后续计划。 + +**阻塞驱动,不追求完美。** 遇到问题修到能继续往下走即可,不做全面重构或优化。目标是跑通闭环,不是打磨每个细节。 + +## Requirements + +**部署就绪** + +R1. 后端服务可在 Docker Compose 环境中启动,所有 API 端点可访问 + +R2. 前端构建产物可被部署并提供页面访问 + +R3. 数据库迁移可从空库执行到最新版本,所有表和索引正确创建 + +R4. 环境变量配置完整,第三方服务密钥缺失时优雅降级为 mock 模式 + +**核心流程可跑通** + +R5. 新用户可完成注册并登录,获得有效 JWT + +R6. 登录用户可创建品牌,触发诊断,获得非零健康分 + +R7. 免费用户访问付费功能时触发付费墙,显示升级提示 + +R8. 用户可发起支付(mock 模式),支付完成后配额刷新、付费功能解锁 + +R9. 公开健康分页面无需注册即可访问,输入品牌名可生成报告 + +**端到端验证** + +R10. 完整变现闭环可在部署环境中走通:注册→诊断→健康分→付费墙→支付→解锁 + +**代码质量(2026-06-01 复盘新增)** + +R11. Dockerfile 健康检查端点与实际 API 端点一致(当前 `/api/health` 不存在,应为 `/health`) + +R12. 测试环境配置完整,`.env.test` 包含必需的 JWT_SECRET + +R13. 前端页面统一使用 API 客户端,认证 token 正确传递(reports、lifecycle/new 页面绕过了统一客户端) + +## Key Flows + +- F1. 完整变现闭环验证 + - **Trigger:** 新用户访问平台 + - **Steps:** 注册账号 → 登录 → 创建品牌 → 触发诊断 → 查看非零健康分 → 尝试付费功能 → 触发付费墙 → 发起 mock 支付 → 支付完成 → 付费功能解锁 + - **Outcome:** 用户完成从获客到付费的完整链路 + - **Covers:** R5, R6, R7, R8, R10 + +- F2. 公开健康分获客路径 + - **Trigger:** 未注册用户访问公开健康分页面 + - **Steps:** 输入品牌名 → 系统生成 GEO 健康分报告 → 显示关键指标和问题 → 引导注册查看完整报告 + - **Outcome:** 用户被引导进入注册流程 + - **Covers:** R9, R10 + +## Scope Boundaries + +**In scope:** +- Docker Compose 部署配置 +- 数据库迁移执行 +- 核心流程端到端验证 +- 阻塞问题修复 + +**Deferred for later:** +- 真实微信/支付宝 SDK 接入 +- CI/CD 流水线 +- 性能优化和压力测试 +- 生产环境域名和 HTTPS +- 完整测试覆盖 + +**Outside this sprint:** +- UI 打磨和视觉优化 +- 新功能开发 +- 代码重构 + +## Dependencies / Assumptions + +- PostgreSQL 15 + pgvector 扩展可用(Docker 容器已配置) +- Redis 7 可用(会话和缓存) +- 第三方 API 密钥(DeepSeek、OpenAI 等)可能未配置,系统需支持 mock 降级 +- LLM API 调用成本可控(诊断和内容生成会消耗 token) + +## Outstanding Questions + +**Resolved:** +- Docker Compose 配置已完整,包含 db (PostgreSQL 15) + redis (Redis 7) + backend (FastAPI) + frontend (Next.js) 四个服务,开发和生产两套配置均就绪 +- 数据库迁移策略:使用 `create_all` + `stamp head`(KTD2) +- 前端部署方式:独立容器,开发模式用 `npm run dev`,生产模式用 standalone `node server.js` +- pgvector 扩展安装:通过 init-db.sh 从源码编译安装(KTD3) +- 验证方式:本地裸跑优先,Docker 部署后续(KTD6) + +**Open:** +- 前端 reports 和 lifecycle/new 页面绕过统一 API 客户端,是否在本次修复?(当前标记为 deferred) diff --git a/docs/plans/2026-06-01-004-chore-geo-launch-readiness-sprint-plan.md b/docs/plans/2026-06-01-004-chore-geo-launch-readiness-sprint-plan.md new file mode 100644 index 0000000..12acc03 --- /dev/null +++ b/docs/plans/2026-06-01-004-chore-geo-launch-readiness-sprint-plan.md @@ -0,0 +1,298 @@ +--- +title: "chore: GEO Platform Launch Readiness Sprint" +type: chore +status: active +date: "2026-06-01" +origin: docs/brainstorms/2026-06-01-geo-launch-readiness-requirements.md +--- + +## Summary + +修复部署阻塞问题,启动服务,端到端验证完整变现闭环(注册→诊断→健康分→付费墙→支付→解锁),使 GEO 平台达到可上线状态。 + +## Problem Frame + +Plan 003 完成了 9 个实施单元的代码编写,但这些代码从未作为完整系统运行过。研究发现 4 个 P0 阻塞问题导致服务无法启动:JWT_SECRET 长度不足、DATABASE_URL 驱动不匹配、根目录 .env 缺失、pgvector 扩展未安装。在代码从未跑过的状态下,任何单点修复都是盲目的;只有让系统先跑起来,才能发现真正的集成问题。 + +## Requirements + +**部署就绪** + +R1. 后端服务可在 Docker Compose 环境中启动,所有 API 端点可访问 + +R2. 前端构建产物可被部署并提供页面访问 + +R3. 数据库迁移可从空库执行到最新版本,所有表和索引正确创建 + +R4. 环境变量配置完整,第三方服务密钥缺失时优雅降级为 mock 模式 + +**核心流程可跑通** + +R5. 新用户可完成注册并登录,获得有效 JWT + +R6. 登录用户可创建品牌,触发诊断,获得非零健康分 + +R7. 免费用户访问付费功能时触发付费墙,显示升级提示 + +R8. 用户可发起支付(mock 模式),支付完成后配额刷新、付费功能解锁 + +R9. 公开健康分页面无需注册即可访问,输入品牌名可生成报告 + +**端到端验证** + +R10. 完整变现闭环可在部署环境中走通:注册→诊断→健康分→付费墙→支付→解锁 + +**代码质量** + +R11. Dockerfile 健康检查端点与实际 API 端点一致 + +R12. 测试环境配置完整,测试可正常启动 + +R13. 前端页面统一使用 API 客户端,认证 token 正确传递 + +## Key Technical Decisions + +KTD1. **DATABASE_URL 使用 asyncpg 驱动。** `database.py` 使用 `create_async_engine`,必须用 `postgresql+asyncpg://` 而非 `postgresql+psycopg://`。当前 `backend/.env` 中的值使用了错误的同步驱动。 + +KTD2. **数据库初始化使用 create_all + stamp head。** Alembic 迁移链存在顺序问题(alerts 表引用 brands 但 brands 在更晚的迁移中创建),直接 `alembic upgrade head` 会失败。先用 `Base.metadata.create_all()` 创建所有表,再用 `alembic stamp head` 标记版本。这与前序会话中验证过的策略一致。 + +KTD3. **pgvector 通过 PostgreSQL 初始化脚本安装。** 在 Docker Compose 中为 db 服务添加初始化脚本,从源码编译安装 pgvector 扩展。避免构建自定义 PostgreSQL 镜像的复杂性。 + +KTD4. **支付/分发/邮件保持 mock 模式。** 本次验证目标是确认付费墙逻辑正确,真实 SDK 接入是后续工作。`PAYMENT_MODE=mock`、`DISTRIBUTION_MODE=mock`、`EMAIL_MODE=mock`。 + +KTD5. **开发环境优先于生产环境。** 先用 `docker-compose.yml`(开发模式)验证,确认跑通后再配置 `docker-compose.prod.yml`。开发模式挂载源码目录便于调试。 + +KTD6. **本地裸跑优先于 Docker 部署。** Docker Hub 网络问题导致镜像构建失败,先用本地直接运行后端/前端验证核心流程,Docker 部署作为后续工作。 + +KTD7. **验证驱动修复。** 手动走完注册→诊断→支付全链路,每步发现问题就修,不做预防性重构。 + +## Implementation Units + +### U1. Fix environment configuration blockers ✅ + +**Goal:** 修复所有 P0 阻塞问题,使服务可以启动 + +**Requirements:** R4 + +**Status:** Completed (previous session) + +**What was done:** +- `backend/.env` DATABASE_URL 改为 `postgresql+asyncpg://` +- `JWT_SECRET` 改为 43 字符 +- 从 `.env.example` 创建根目录 `.env` +- `.gitignore` 确认排除 `.env` + +--- + +### U2. Database setup with pgvector and migrations ✅ + +**Goal:** PostgreSQL 容器安装 pgvector 扩展,数据库表结构完整创建 + +**Requirements:** R3 + +**Status:** Completed (previous session) + +**What was done:** +- 创建 `backend/init-db.sh` pgvector 初始化脚本 +- `docker-compose.yml` 挂载初始化脚本 +- 创建 `backend/init_schema.py` 使用 create_all + stamp head +- 数据库表和 pgvector 扩展已验证存在 + +--- + +### U3. Service startup and health verification 🔄 + +**Goal:** 后端和前端服务均可启动并通过健康检查 + +**Requirements:** R1, R2 + +**Status:** Partially completed (previous session) + +**What was done:** +- 后端本地启动成功:`uvicorn app.main:app --host 0.0.0.0 --port 8000` +- 后端健康检查通过:`/health` → healthy, `/ready` → database:ok, redis:ok +- 前端本地启动成功:`npm run dev` on port 3001 + +**Remaining:** +- 验证前端页面可访问(`curl http://localhost:3001`) +- 验证前端可调用后端 API(无 CORS 错误) + +--- + +### U3.5. Fix audit-discovered P0 issues + +**Goal:** 修复复盘发现的 P0 阻塞问题 + +**Requirements:** R11, R12 + +**Dependencies:** none (可并行) + +**Files:** +- `backend/Dockerfile` — 修复健康检查端点 `/api/health` → `/health` +- `backend/.env.test` — 添加 JWT_SECRET + +**Approach:** +1. 修改 Dockerfile HEALTHCHECK 端点从 `/api/health` 改为 `/health` +2. 在 `.env.test` 中添加 `JWT_SECRET=test-jwt-secret-for-testing-at-least-32-characters-long` + +**Test scenarios:** +- Dockerfile 健康检查端点与 FastAPI 注册的 `/health` 一致 +- `pytest` 可正常启动(不因 JWT_SECRET 缺失而 sys.exit) + +**Verification:** Dockerfile HEALTHCHECK CMD 正确,测试可运行 + +--- + +### U4. Authentication flow verification + +**Goal:** 新用户可完成注册、登录、获取 JWT + +**Requirements:** R5 + +**Dependencies:** U3 + +**Files:** +- `backend/app/api/auth.py` — 认证 API +- `backend/app/services/auth.py` — 认证服务 +- `backend/app/models/user.py` — User 模型 +- `frontend/app/(auth)/` — 前端认证页面 + +**Known issues:** +- 前端部分页面绕过统一 API 客户端(reports、lifecycle/new),认证 token 可能不被附加 + +**Approach:** +1. 启动后端和前端服务 +2. 通过 API 注册新用户:`POST /api/v1/auth/register` +3. 验证用户数据正确写入数据库(plan=free, max_queries=5) +4. 登录获取 JWT token:`POST /api/v1/auth/login` +5. 用 JWT token 调用受保护 API:`GET /api/v1/brands` +6. 修复认证流程中的任何错误 + +**Test scenarios:** +- `POST /api/v1/auth/register` 创建用户成功,返回 201 +- `POST /api/v1/auth/login` 返回 JWT token +- `GET /api/v1/brands` 携带 JWT 返回 200(非 401) +- 新用户 plan 字段为 "free",max_queries 为 5 + +**Verification:** 新用户可完成注册→登录→访问受保护资源 + +--- + +### U5. Diagnosis and health score verification + +**Goal:** 用户可创建品牌、触发诊断、获得非零健康分;公开健康分页面可访问 + +**Requirements:** R6, R9 + +**Dependencies:** U4 + +**Files:** +- `backend/app/api/diagnosis.py` — 诊断 API +- `backend/app/api/health_score.py` — 公开健康分 API +- `backend/app/services/diagnosis/` — 诊断服务 +- `frontend/app/(public)/health-score/` — 公开健康分页面 +- `frontend/app/(dashboard)/onboarding/` — Onboarding 页面 + +**Known risks:** +- 诊断依赖 DeepSeek API,如果 API Key 无效或额度耗尽,诊断会返回空结果 +- 诊断是异步流程,可能需要轮询等待结果 + +**Approach:** +1. 登录用户创建品牌 +2. 触发诊断,验证数据采集流程(AI 平台查询 + CitationRecord 分析) +3. 查看诊断结果,确认健康分为非零值 +4. 访问公开健康分页面,输入品牌名生成报告 +5. 修复诊断流程中的任何错误(LLM API 调用失败、数据采集空结果等) + +**Test scenarios:** +- `POST /api/v1/brands` 创建品牌成功 +- `POST /api/v1/diagnosis` 触发诊断,返回诊断任务 ID +- `GET /api/v1/diagnosis/{id}` 返回非零健康分 +- `GET /api/v1/public/health-score?brand={name}` 返回公开健康分报告 +- 公开健康分页面无需登录即可访问 + +**Verification:** 用户可获得非零 GEO 健康分,公开页面可生成报告 + +--- + +### U6. Monetization closed loop verification + +**Goal:** 完整变现闭环可走通——免费用户触发付费墙、mock 支付、功能解锁 + +**Requirements:** R7, R8, R10 + +**Dependencies:** U5 + +**Files:** +- `backend/app/middleware/subscription_enforcement.py` — 订阅限制中间件 +- `backend/app/api/payments.py` — 支付 API +- `backend/app/services/payment/` — 支付服务 +- `frontend/components/subscription/` — 订阅 UI 组件 +- `frontend/app/(dashboard)/dashboard/` — Dashboard 页面 + +**Approach:** +1. 免费用户尝试访问付费功能(如 AI 内容生成、高级诊断),验证付费墙触发 +2. 验证升级提示正确显示 +3. 发起 mock 支付,确认支付流程完成 +4. 验证支付后用户 plan 升级、配额刷新 +5. 验证付费功能解锁 +6. 修复变现闭环中的任何错误 + +**Test scenarios:** +- 免费用户访问付费 API 返回 403 + 升级提示 +- `POST /api/v1/payments/create` 创建支付订单 +- Mock 支付回调后用户 plan 从 "free" 变为 "pro" +- 付费功能解锁,用户可正常使用 + +**Verification:** 完整变现闭环走通——注册→诊断→付费墙→支付→解锁 + +--- + +## Scope Boundaries + +**In scope:** +- 修复部署阻塞问题 +- 数据库初始化和迁移 +- 服务启动验证 +- 核心流程端到端验证 +- 验证过程中发现的阻塞 bug 修复 +- 审计发现的 P0 问题修复 + +**Deferred for later:** +- 真实微信/支付宝 SDK 接入 +- CI/CD 流水线 +- 性能优化和压力测试 +- 生产环境域名和 HTTPS 配置 +- `.env.production` 文件创建 +- Redis 密码保护 +- PostgreSQL 弱密码更换 +- pgvector 镜像优化(改用 pgvector/pgvector:pg15) +- 前端统一 API 客户端修复(reports、lifecycle/new 页面) +- JWT_SECRET 强密钥生成 + +**Outside this sprint:** +- UI 打磨和视觉优化 +- 新功能开发 +- 代码重构 +- 完整测试覆盖 + +## Risks & Dependencies + +- **LLM API 可用性**:诊断和内容生成依赖 DeepSeek API,如果 API Key 无效或额度耗尽,诊断会返回空结果。需确认 API Key 有效。 +- **pgvector 编译时间**:从源码编译 pgvector 需要安装 build-essential 和 git,首次启动数据库容器可能需要 2-3 分钟。 +- **迁移链顺序问题**:Alembic 迁移链可能存在未发现的顺序依赖,create_all + stamp head 策略可绕过此问题。 +- **前端构建问题**:ESLint 警告已降级为 warn,但可能存在运行时错误仅在浏览器中暴露。 +- **前端 API 客户端不一致**:部分页面绕过统一 API 客户端,认证 token 可能不被附加,导致 401 错误。 +- **Docker Hub 网络问题**:国内环境拉取 Docker 镜像可能超时,影响 Docker Compose 部署。 + +## Sources & Research + +- `backend/.env` — 当前环境变量配置,DATABASE_URL 驱动和 JWT_SECRET 已修复 +- `backend/app/config.py` — JWT_SECRET >= 32 字符校验逻辑 +- `backend/app/database.py` — async engine 配置,确认需要 asyncpg 驱动 +- `backend/app/main.py` — FastAPI 入口,28+ API 路由注册,6 层中间件栈 +- `backend/Dockerfile` — 健康检查端点错误(/api/health → /health) +- `docker-compose.yml` — 开发环境 4 服务配置,backend environment 覆盖 +- `docker-compose.prod.yml` — 生产环境配置,依赖 `.env.production` +- 前序会话 pgvector 安装经验:从源码编译 v0.5.1 +- 2026-06-01 全面复盘审计:发现 Dockerfile 健康检查、.env.test、前端 API 客户端等问题