一个实验项目,尝试用Codex完成一个有一定复杂度的原型产品。
我的输入几乎都在ROOT.md和SPEC.md两个文档里,另外还有对话过程中的指令,其它文件都是Codex产生的。
Codex的配置是:GPT-5.3-Codex / Extra High
Everything about AI
根据 SPEC.md 实现的最小可用骨架,包含:
- FastAPI 后端(注册/登录/退出、忘记密码/重置密码、个人资料、Google SSO)
- Next.js 前端(全局导航、首页品牌化设计、认证页、资料页、忘记/重置密码页)
- 管理系统(管理员初始化、管理入口、用户账号管理)
- PostgreSQL + Redis + Docker Compose
- Alembic 数据库迁移
apps/api: FastAPI 服务apps/web: Next.js Web 前端infra/docker-compose.yml: 本地一键启动
- 准备环境变量:
cp .env.example .env- 启动:
docker compose -f infra/docker-compose.yml up --build- 访问:
- Web: http://localhost:3000
- API health: http://localhost:8000/healthz
- 准备生产变量:
cp .env.example .env- 至少修改以下环境变量(
.env):
APP_DOMAIN(例如note.example.com)SECRET_KEYADMIN_PASSWORDPOSTGRES_PASSWORDNEXT_PUBLIC_API_BASE_URL=/api/v1
- 启动生产编排(含 Caddy 反向代理 + HTTPS):
docker compose --env-file .env -f infra/docker-compose.prod.yml up -d --build- 开放安全组端口:
80/tcp443/tcp22/tcp
说明:
- 外部流量统一走 Caddy(80/443),
api/db/redis/web不直接暴露公网端口。 - 默认前端 API 地址为同域路径
/api/v1,无需让浏览器直连8000。
- 更新部署(拉取最新代码 + 重建 + 重启):
./infra/scripts/prod-update.sh- 默认使用当前分支,也可指定分支:
./infra/scripts/prod-update.sh main - 脚本会执行
docker compose ... up -d --build,并在 API 启动时自动跑 Alembic migration。
- 清空业务数据并重置(删除 PostgreSQL/Redis 数据卷后重建):
./infra/scripts/prod-reset-data.sh --yes- 默认保留 Caddy 证书数据。
- 如需同时删除证书并重新签发:
RESET_CERTS=1 ./infra/scripts/prod-reset-data.sh --yes
/api/v1/auth/register/api/v1/auth/send-register-email-code/api/v1/auth/login/api/v1/auth/logout/api/v1/auth/forgot-password/api/v1/auth/reset-password/api/v1/me(GET/PATCH)/api/v1/admin/users(GET)/api/v1/admin/users/{user_id}(PATCH)/api/v1/admin/notes(GET)/api/v1/admin/notes/{note_id}(DELETE)/api/v1/admin/notes/{note_id}/restore(POST)/api/v1/admin/sources(GET/POST)/api/v1/admin/sources/{source_id}(PATCH/DELETE)/api/v1/admin/sources/{source_id}/restore(POST)/api/v1/admin/aggregates/refresh(POST, 后台任务入队)/api/v1/admin/aggregates/refresh/{job_id}(GET, 查询任务状态)/api/v1/notes(POST/GET)/api/v1/notes/{note_id}(GET/PATCH)/api/v1/notes/{note_id}/reanalyze(POST)/api/v1/notes/public/{note_id}(GET)/api/v1/auth/sso/google/start(发起 Google 授权)/api/v1/auth/sso/google/callback(Google 回调)/api/v1/auth/sso/google/complete(首次登录资料补全)
- 在 Google Cloud Console 创建 OAuth Client(Web application):
- 进入
APIs & Services -> OAuth consent screen,先完成 consent screen 基础配置 - 建议先用
Testing状态,并在Test users里加入测试账号 - 进入
Credentials,创建OAuth client ID(类型选择 Web)
- 配置回调与来源地址:
Authorized redirect URIs添加:http://localhost:8000/api/v1/auth/sso/google/callback
Authorized JavaScript origins添加:http://localhost:3000
- 配置后端环境变量(
.env):
GOOGLE_OAUTH_CLIENT_ID=你的_client_id
GOOGLE_OAUTH_CLIENT_SECRET=你的_client_secret
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:8000/api/v1/auth/sso/google/callback
GOOGLE_OAUTH_SCOPE=openid profile email
GOOGLE_OAUTH_STATE_TTL_SECONDS=600
GOOGLE_OAUTH_COMPLETE_TTL_SECONDS=900
GOOGLE_OAUTH_TIMEOUT_SECONDS=10- 启动服务并验证:
- 打开 Web 认证页:
http://localhost:3000/auth - 点击
使用 Google 账号登录 - 首次登录(系统里无同邮箱账号)会跳转到资料补全页,填写
ID后进入系统 - 已有同邮箱账号会自动绑定并直接登录
- 常见问题排查:
- 报错
redirect_uri_mismatch:检查 Google Console 与.env的GOOGLE_OAUTH_REDIRECT_URI是否完全一致 - 回调后提示状态失效:检查浏览器是否阻止重定向,或等待时间过长导致 state 过期
- 显示
Google SSO 未配置:确认 API 容器已读取到上述 Google 环境变量
- 推荐部署形态(同域反向代理):
- Web:
https://note.example.com - API:
https://note.example.com/api/v1 - Google 回调:
https://note.example.com/api/v1/auth/sso/google/callback
- Google Cloud Console(生产):
Authorized redirect URIs添加:https://note.example.com/api/v1/auth/sso/google/callback
Authorized JavaScript origins添加:https://note.example.com
.env参考(生产):
WEB_BASE_URL=https://note.example.com
NEXT_PUBLIC_API_BASE_URL=/api/v1
GOOGLE_OAUTH_CLIENT_ID=prod_xxx.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=prod_secret_xxx
GOOGLE_OAUTH_REDIRECT_URI=https://note.example.com/api/v1/auth/sso/google/callback
GOOGLE_OAUTH_SCOPE=openid profile email
GOOGLE_OAUTH_STATE_TTL_SECONDS=600
GOOGLE_OAUTH_COMPLETE_TTL_SECONDS=900
GOOGLE_OAUTH_TIMEOUT_SECONDS=10- 若 Web 与 API 分域(不推荐首版):
- 例如 Web:
https://app.example.com,API:https://api.example.com - 则
GOOGLE_OAUTH_REDIRECT_URI应配置为:https://api.example.com/api/v1/auth/sso/google/callback
- 同时
WEB_BASE_URL必须是:https://app.example.com
- 上线前检查清单:
GOOGLE_OAUTH_REDIRECT_URI与 Google Console 配置逐字符一致(包含协议、域名、路径)- 回调地址必须使用
https - OAuth consent screen 的发布状态与测试账号范围符合预期
- 生产密钥不写入代码库,仅通过环境变量注入
- 启动 API 容器时会自动执行:
alembic upgrade head - 手动执行迁移(在
apps/api目录):
alembic upgrade head- 生成新迁移(在
apps/api目录):
alembic revision -m "your migration name"- 忘记密码发信账号默认配置为
llm_notebook@163.com。 - 若未配置 SMTP,后端会跳过真实发信并写日志。
- 数据库结构由 Alembic 版本管理,不再使用
create_all自动建表。 - 系统启动会自动确保管理员账号存在,默认读取以下环境变量:
ADMIN_USER_IDADMIN_EMAILADMIN_PASSWORDADMIN_NICKNAME
- 若未覆盖,默认管理员为:
ADMIN_USER_ID=adminADMIN_PASSWORD=ChangeMe123!(建议首登后立刻修改)
- 注册邮箱验证码相关环境变量:
REGISTER_EMAIL_CODE_TTL_SECONDSREGISTER_EMAIL_CODE_COOLDOWN_SECONDSREGISTER_EMAIL_CODE_MAX_ATTEMPTS
- Google SSO 相关环境变量:
GOOGLE_OAUTH_CLIENT_IDGOOGLE_OAUTH_CLIENT_SECRETGOOGLE_OAUTH_REDIRECT_URI(需与 Google Cloud Console 保持一致)GOOGLE_OAUTH_SCOPE(默认openid profile email)GOOGLE_OAUTH_STATE_TTL_SECONDSGOOGLE_OAUTH_COMPLETE_TTL_SECONDSGOOGLE_OAUTH_TIMEOUT_SECONDS
- 内容分析模型相关环境变量(支持
OpenAI/Gemini/Claude接口风格):LLM_PROVIDER_NAME(默认openai,可选openai/gemini/claude)LLM_BASE_URL(可选,不填时按 provider 使用官方默认端点)LLM_API_KEYLLM_MODEL_NAMELLM_TIMEOUT_SECONDSLLM_MAX_RETRIESLLM_PROMPT_VERSIONNETWORK_PROXY_URL(全局网络代理,影响链接抓取与大模型调用)CONTENT_FETCH_USE_JINA_READER(全局抓取策略开关,true使用 Jina Reader,false直连来源链接)JINA_READER_BASE_URL(Jina Reader 前缀地址,默认https://r.jina.ai/)JINA_READER_TOKEN(可选,配置后以Authorization: Bearer <token>方式访问 Jina Reader;为空则匿名访问)
- 信息聚合相关环境变量:
AGGREGATION_MAX_ITEMS_PER_SOURCE(每个信息源每次刷新最多处理的候选链接数)AGGREGATION_REFRESH_JOB_TTL_SECONDS(聚合刷新任务状态在 Redis 的保留时长,单位秒)