diff --git a/README.md b/README.md index bdd0f12..16bbe12 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Python 3.12 / Django 5.2 LTS + DRF / django-admin / 用户端 Django 模板 SSR ## 当前状态 -Phase 2 计费核心已完成,Phase 3 对外 API 与充值已完成到 T-306,Phase 4 用户端已完成 T-501~T-505,Phase 5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档,Phase 6 已完成 T-601 可用别名发现与 T-602 django-admin 分组/表名中文化;T-603 字段级中文化代码和 no-op 迁移已完成,但完整测试被工作区既有邮箱验证配置改动阻塞,暂未标 DONE。用户可通过 allauth 自助注册、邮箱验证、登录、登出,扫码充值并轮询到账,生成 / 删除(吊销)API Key,查看余额、充值总额、分页充值记录、分页消费记录与可用模型;运营可在 django-admin 检索用户、钱包、API Key、计费规则、汇率、充值订单、点数流水和调用记录,并通过计费层带原因手工调点;生产部署按 `docs/deployment.md` 执行。生产侧仍需配置真实邮件、支付、AI 模型和图片真实耗时验证。详见 [`docs/current-state.md`](docs/current-state.md)。 +Phase 2 计费核心已完成,Phase 3 对外 API 与充值已完成到 T-306,Phase 4 用户端已完成 T-501~T-505,Phase 5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档,Phase 6 已完成 T-601 可用别名发现、T-602 django-admin 分组/表名中文化与 T-605 免邮箱验证策略落地;T-603 字段级中文化代码和 no-op 迁移已完成,待稳定 MySQL 测试库补跑完整验证后收尾。用户可通过 allauth 自助注册(免邮箱验证、注册即可用,邮箱仍必填且唯一)、登录、登出,扫码充值并轮询到账,生成 / 删除(吊销)API Key,查看余额、充值总额、分页充值记录、分页消费记录与可用模型;运营可在 django-admin 检索用户、钱包、API Key、计费规则、汇率、充值订单、点数流水和调用记录,并通过计费层带原因手工调点;生产部署按 `docs/deployment.md` 执行。生产侧仍需配置真实支付、AI 模型和图片真实耗时验证;邮件服务仅用于后续密码找回/通知等邮件能力,不阻塞注册登录。详见 [`docs/current-state.md`](docs/current-state.md)。 > ⚠️ 涉及资金/点数。改动充值、扣费、退款、对账相关代码前,先读 [`docs/05-coding-rules.md`](docs/05-coding-rules.md) 第 8 节与 [`docs/04-architecture.md`](docs/04-architecture.md) 第四节计费时序。 diff --git a/apps/portal/tests.py b/apps/portal/tests.py index 80f120c..9e29790 100644 --- a/apps/portal/tests.py +++ b/apps/portal/tests.py @@ -124,7 +124,7 @@ class PortalAccountFlowTests(TestCase): html, ) - def test_signup_creates_unverified_user_wallet_with_zero_points_and_no_ledger(self): + def test_signup_creates_user_wallet_with_zero_points_no_ledger_and_can_login(self): suffix = uuid.uuid4().hex[:8] email = f"signup-{suffix}@example.com" @@ -146,9 +146,10 @@ class PortalAccountFlowTests(TestCase): self.assertTrue(email_address.primary) self.assertEqual(wallet.points_balance, 0) self.assertFalse(PointsLedger.objects.filter(user=user).exists()) - self.assertEqual(len(mail.outbox), 1) + self.assertEqual(len(mail.outbox), 0) + self.assertTrue(get_user(self.client).is_authenticated) - def test_unverified_email_cannot_establish_login_session(self): + def test_user_can_login_without_verified_email(self): suffix = uuid.uuid4().hex[:8] user = get_user_model().objects.create_user( username=f"unverified-{suffix}", @@ -162,11 +163,16 @@ class PortalAccountFlowTests(TestCase): primary=True, ) - self.client.post("/login", {"login": user.email, "password": self.password}) + login_response = self.client.post( + "/login", + {"login": user.email, "password": self.password}, + ) - self.assertFalse(get_user(self.client).is_authenticated) + self.assertEqual(login_response.status_code, 302) + self.assertEqual(login_response["Location"], "/dashboard") + self.assertTrue(get_user(self.client).is_authenticated) - def test_verified_user_can_login_view_dashboard_and_logout(self): + def test_user_can_login_view_dashboard_and_logout(self): user = self.create_verified_user() UserWallet.objects.create(user=user, points_balance=42) diff --git a/config/settings.py b/config/settings.py index db902c9..5262c0e 100644 --- a/config/settings.py +++ b/config/settings.py @@ -191,11 +191,11 @@ LOGIN_REDIRECT_URL = "/dashboard" LOGOUT_REDIRECT_URL = "/login" ACCOUNT_ADAPTER = "apps.portal.adapters.CmhubAccountAdapter" -ACCOUNT_EMAIL_VERIFICATION = "mandatory" +ACCOUNT_EMAIL_VERIFICATION = "none" ACCOUNT_UNIQUE_EMAIL = True ACCOUNT_LOGIN_METHODS = {"email", "username"} ACCOUNT_SIGNUP_FIELDS = ["username*", "email*", "password1*", "password2*"] -ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True +ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = False ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = "/login" ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = "/dashboard" ACCOUNT_EMAIL_SUBJECT_PREFIX = "[cmhub] " diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md index ec3959d..0c1420c 100644 --- a/docs/00-ai-start-here.md +++ b/docs/00-ai-start-here.md @@ -38,7 +38,7 @@ ## 当前阶段 -当前项目处于:**Phase 6 增强任务收尾期**。Phase 2 计费核心已完成到 T-204;Phase 3 已完成 T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调、T-305 扫码充值下单 + 轮询与 T-306 对外 API 安全加固;Phase 4 已完成 T-501 注册 / 登录(allauth)、T-502 API Key 自助管理页、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化;Phase 5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档;Phase 6 已完成 T-601「可用别名发现」与 T-602「django-admin 中文化第 1-3 层」。T-603「django-admin 字段级中文化」代码和迁移已完成,但完整测试被工作区既有 `ACCOUNT_EMAIL_VERIFICATION="none"` 配置改动阻塞,需恢复 `mandatory` 后重跑测试再标 `DONE`。生产侧仍需按 `deployment.md` 配置真实邮件、支付、AI 模型和图片真实耗时验证。 +当前项目处于:**Phase 6 增强任务收尾期**。Phase 2 计费核心已完成到 T-204;Phase 3 已完成 T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调、T-305 扫码充值下单 + 轮询与 T-306 对外 API 安全加固;Phase 4 已完成 T-501 注册 / 登录(allauth)、T-502 API Key 自助管理页、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化;Phase 5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档;Phase 6 已完成 T-601「可用别名发现」、T-602「django-admin 中文化第 1-3 层」与 T-605「免邮箱验证策略落地」。T-603「django-admin 字段级中文化」代码和迁移已完成,待稳定 MySQL 测试库补跑完整验证后收尾。生产侧仍需按 `deployment.md` 配置真实支付、AI 模型和图片真实耗时验证;邮件服务仅用于后续密码找回/通知等邮件能力,不阻塞注册登录。 优先路径: @@ -48,7 +48,7 @@ 4. Phase 3:对外 API 与充值 —— T-301 Key 鉴权、T-302 生成接口、T-303 余额查询、T-304 充值回调、T-305 扫码下单与轮询、T-306 安全加固已完成。 5. Phase 4:用户端(Django 模板 SSR)—— T-501 注册登录、T-502 API Key 管理、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化已完成。 6. Phase 5:后台与发布 —— T-401 运营后台完善、T-402 完整验收 MVP、T-403 部署 / 运行文档已完成;计划内 MVP 任务已收尾。 -7. Phase 6:增强(MVP 后)—— T-601 可用别名发现已完成,实现 `/api/v1/models` 与 portal 只读「可用模型」页;T-602 已完成 django-admin 分组/表名中文化;T-603 已完成字段级中文标签代码与 no-op 迁移,当前因邮箱验证配置改动导致完整测试未全绿,状态为 `BLOCKED`。 +7. Phase 6:增强(MVP 后)—— T-601 可用别名发现已完成,实现 `/api/v1/models` 与 portal 只读「可用模型」页;T-602 已完成 django-admin 分组/表名中文化;T-605 已完成免邮箱验证策略落地;T-603 已完成字段级中文标签代码与 no-op 迁移,待稳定 MySQL 测试库补跑完整验证。 ## 领取任务规则 diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index b47afc4..071d579 100644 --- a/docs/03-tech-stack.md +++ b/docs/03-tech-stack.md @@ -11,7 +11,7 @@ | Web 框架 | Django 5.2 LTS | 已定 | 自带 ORM、迁移、admin,适合「API + 运营后台」;选 LTS 维护到 2028,安全更新窗口最长。**禁用已 EOL 的 4.0/4.1**(无安全补丁,资金服务不可用) | | API 框架 | Django REST Framework (DRF) | 已定 | 鉴权、序列化、参数校验、限流现成 | | 运营后台 | django-admin | 已定 | 近零代码即得用户/点数/记录的增删改查与检索,省 80% 后台工作量 | -| 用户端 | Django 模板 SSR + Bootstrap 5 + django-allauth | 已定 | T-501 已落地自助注册/登录/登出、邮箱验证与最小 dashboard;T-502 已落地 API Key 自助生成 / 删除页;T-503 已落地个人中心、充值记录和消费记录页;T-504 已落地充值页(创建订单、展示二维码票据、轮询到账后刷新);T-505 已把 Bootstrap 与 qrcode.js 改为本地 static 自托管,并给记录页加分页;MVP 先用 Django form + Bootstrap 模板,不为简单表单引入 crispy-forms | +| 用户端 | Django 模板 SSR + Bootstrap 5 + django-allauth | 已定 | T-501 已落地自助注册/登录/登出与最小 dashboard;T-605 起注册策略固定为**免邮箱验证、注册即可用,邮箱仍必填且唯一**;T-502 已落地 API Key 自助生成 / 删除页;T-503 已落地个人中心、充值记录和消费记录页;T-504 已落地充值页(创建订单、展示二维码票据、轮询到账后刷新);T-505 已把 Bootstrap 与 qrcode.js 改为本地 static 自托管,并给记录页加分页;MVP 先用 Django form + Bootstrap 模板,不为简单表单引入 crispy-forms | | 后台美化 | django-unfold 或 simpleui | 待定 | 仅外观,MVP 可先用原生 admin,后期按需引入 | | AI 上游对接 | **Provider 适配器层**(按 `api_type` 注册)+ **能力别名** 映射 + `requests` HTTP 客户端 | 已定 | 对外只暴露 `generate text/image` 两接口与别名;换供应商改后台映射,不动对外契约。移植 `cmbot` 的调用逻辑到各适配器。当前 3 模型机制不同:文本 chat、`nano-banana2` chat 多模态返图、`gpt-image-2` images/edits 改图(详见 `04` 3.1) | | 供应商密钥存储 | 应用层 Fernet 加密(`cryptography`) | 已定 | `AiModel.api_key_encrypted` 加密入库、admin 写入型字段不回显;加密主密钥 `AI_KEY_ENCRYPTION_KEY` 走环境变量,配置清单见 `env.md` | @@ -20,7 +20,7 @@ | 数据库 | MySQL 8.4 LTS(cmhub 专用独立实例) | 已定 | 满足 Django 5.2 的 MySQL ≥8.0.11;引擎 InnoDB + 字符集 utf8mb4;行锁 `select_for_update` / 条件更新保并发扣点。**不复用 VPS 已有的 MySQL 5.7**(跑不了 Django 5.2、无 CHECK 约束)。开发亦用 MySQL,勿用 SQLite(不支持 `select_for_update`) | | MySQL 驱动 | PyMySQL + cryptography | 已定 | PyMySQL 负责 Django 连接 MySQL;MySQL 8 默认 `caching_sha2_password` 认证需要 `cryptography` 支持;客户端连接/读/写超时通过 `MYSQL_CONNECT_TIMEOUT` / `MYSQL_READ_TIMEOUT` / `MYSQL_WRITE_TIMEOUT` 配置 | | 对外鉴权 | API Key(DRF 自定义 Authentication,哈希存储比对) | 已定 | 用户自助生成 Key;**API 只认 Key、不挂 SessionAuthentication**,防浏览器 cookie 绕过计费 | -| 用户端鉴权 | Django Session(+ allauth 注册登录邮箱验证) | 已定 | T-501 已落地 `/signup` `/login` `/logout` 与 `/dashboard`;T-502 已落地 `/apikeys`;T-503 已落地 `/records/recharge` 与 `/records/usage`;T-504 已落地 `/recharge`;用户端页面与 `recharge/create` 走 session + CSRF;后台账号也用 Session 登录 | +| 用户端鉴权 | Django Session(+ allauth 注册登录,免邮箱验证) | 已定 | T-501 已落地 `/signup` `/login` `/logout` 与 `/dashboard`;T-605 固定 `ACCOUNT_EMAIL_VERIFICATION="none"`,新用户注册后可直接使用;T-502 已落地 `/apikeys`;T-503 已落地 `/records/recharge` 与 `/records/usage`;T-504 已落地 `/recharge`;用户端页面与 `recharge/create` 走 session + CSRF;后台账号也用 Session 登录 | | 充值对接 | 自助扫码:微信 V3 native + 支付宝当面付;下单取二维码 + 服务端回调(验签 + 幂等) | 已定 | T-304/T-305 已落地回调、扫码下单、状态轮询、HMAC mock 联调与 SDK 模式入口;生产需安装并配置 `wechatpayv3` / `python-alipay-sdk` 与真实商户密钥/证书 | | 生成返回方式 | 同步 HTTP(无任务队列) | 已定 | MVP 简化;图片接口需调大网关/服务超时 | | 任务队列 | 暂不引入(Celery/RQ) | 待定 | V2 异步化时再评估 | @@ -41,7 +41,7 @@ - **同步可用的前提是「超时链路 + worker 容量」配对**,否则会「小量正常、上量假死」:① 每模型 `timeout_seconds` 设有限值(`ai_models.json` 现为 `0`,迁入须改);② Gunicorn `--timeout` 与网关 `proxy_read_timeout` 按最慢图片放大(别用默认 30s / 60s);③ worker/线程数按**峰值总并发**预留。详见 [架构设计](04-architecture.md) 第五节结论。 - **何时转 V2 异步**:worker 被长连接占满拖慢快接口/后台、接入方总并发明显上涨、或需要「关窗重连/任务持久化」体验——在此之前保持同步。 - **数据库:MySQL 8.4 LTS,cmhub 专用独立实例**:① 部署环境的 VPS 已装 MySQL 5.7 供其他服务用,但 5.7 跑不了 Django 5.2(需 ≥8.0.11)、已 EOL、且不支持 CHECK 约束,故**不复用**它;② 机器内存宽裕(`available` 7.4G),给 cmhub **单开一个 MySQL 8.4 LTS 实例**(独立端口/容器),与已有 5.7 完全隔离、互不影响;③ 选 8.4 LTS 取长维护窗口 + 完整 CHECK 约束(CHECK 需 MySQL ≥8.0.16 才真正生效);④ 强制 InnoDB + utf8mb4(5.7/老配置默认非 utf8mb4,prompt 的 emoji/生僻字会写失败);⑤ MySQL 默认隔离级别 REPEATABLE READ(不同于 PostgreSQL 的 READ COMMITTED),`select_for_update` 扣点仍安全,但计费实现按此语义验证;⑥ 开发环境同用 MySQL,不要用 SQLite——SQLite 会静默忽略 `FOR UPDATE`,并发扣点逻辑测不出来;⑦ 不在代码里写死只适配某一种库的 SQL。 -- **用户端用 Django 模板 SSR 单体,不引前端框架**:需求含终端用户自助(注册/充值/API Key/记录),选 Django 模板 + Bootstrap + allauth 与后端同工程单体部署,复用 Django auth/session,开发部署最快、最契合单机 MVP;代价是交互不如 SPA,可后续加 HTMX。T-501 已接入 django-allauth 65.18.0,使用邮箱验证、session 与 CSRF;注册成功只创建 0 点 `UserWallet`,不写赠点流水。T-502 已用 Django Form + Bootstrap 模板落地 `/apikeys`,生成 Key 后明文只显示一次,删除写为 `revoked`。T-503 已用只读 Django TemplateView 落地 dashboard 汇总、充值记录和消费记录。T-504 已用 Django Form + Bootstrap 模板落地 `/recharge`:页面 POST 创建 pending 充值订单,展示支付二维码票据,并用浏览器轮询 `/api/v1/recharge/status`,订单 paid 后刷新余额。T-505 已把 Bootstrap 5 CSS 与 qrcode.js vendoring 到 `apps/portal/static/portal/vendor/`,页面不再依赖 jsdelivr,充值/消费记录页改为 Django `Paginator` 分页。放弃 Vue/React 前后端分离(两套项目/部署,与单体 MVP 调性冲突)。用户模型:`User`(auth) 持登录态、`UserWallet` 持点数(扣点锁 wallet、与 auth 解耦)、`ApiKey`(User 1:N,哈希存储)。注册不送免费点数。 +- **用户端用 Django 模板 SSR 单体,不引前端框架**:需求含终端用户自助(注册/充值/API Key/记录),选 Django 模板 + Bootstrap + allauth 与后端同工程单体部署,复用 Django auth/session,开发部署最快、最契合单机 MVP;代价是交互不如 SPA,可后续加 HTMX。T-501 已接入 django-allauth 65.18.0,使用 session 与 CSRF;T-605 起注册策略固定为 `ACCOUNT_EMAIL_VERIFICATION="none"`,免邮箱验证、注册即可用,邮箱仍必填且唯一;注册成功只创建 0 点 `UserWallet`,不写赠点流水。T-502 已用 Django Form + Bootstrap 模板落地 `/apikeys`,生成 Key 后明文只显示一次,删除写为 `revoked`。T-503 已用只读 Django TemplateView 落地 dashboard 汇总、充值记录和消费记录。T-504 已用 Django Form + Bootstrap 模板落地 `/recharge`:页面 POST 创建 pending 充值订单,展示支付二维码票据,并用浏览器轮询 `/api/v1/recharge/status`,订单 paid 后刷新余额。T-505 已把 Bootstrap 5 CSS 与 qrcode.js vendoring 到 `apps/portal/static/portal/vendor/`,页面不再依赖 jsdelivr,充值/消费记录页改为 Django `Paginator` 分页。放弃 Vue/React 前后端分离(两套项目/部署,与单体 MVP 调性冲突)。用户模型:`User`(auth) 持登录态、`UserWallet` 持点数(扣点锁 wallet、与 auth 解耦)、`ApiKey`(User 1:N,哈希存储)。注册不送免费点数。 ## 三、构建与运行命令 diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 6676a4e..dd87855 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -73,7 +73,7 @@ | --- | --- | --- | --- | --- | | T-401 | 运营后台完善 | T-302, T-304 | admin 可管理用户/钱包/ApiKey(脱敏)、配规则/汇率、检索充值订单/流水/调用记录;手工调点带原因并经计费层写流水 | DONE | | T-402 | 完整验收 MVP | T-401, T-505 | `02-requirements.md` 的 P0 验收全部通过(含用户端注册/充值/API Key/记录) | DONE | -| T-403 | 部署 / 运行文档 | T-402 | 新环境可按文档运行;明确图片接口超时配置;上线前必须引用一次真实图片生成耗时来设置 Gunicorn `--timeout`、Nginx `proxy_read_timeout`、客户端 read timeout;若尚无真实耗时,部署文档必须显式标注图片同步风险未退,不得声称已验证;生产必须配置真实 `DJANGO_EMAIL_BACKEND` / `DJANGO_DEFAULT_FROM_EMAIL`,否则 allauth 邮箱验证无法发信、用户无法完成登录;**生产静态文件 serving**:T-505 已把 Bootstrap/qrcode 自托管到 `apps/portal/static/`,但 `settings` 目前只有 `STATIC_URL`,`DEBUG=False` 下 Django 不发静态文件——必须配置 `STATIC_ROOT` + `collectstatic` + WhiteNoise 或 Nginx 托管 `/static/`,否则用户端 CSS 错版、充值二维码 404(P2-1 的国内可用性目标在生产失效);**DRF 限流依赖共享缓存**:多 Gunicorn worker 下默认 `LocMemCache` 是每进程的,会让 `generate`/`api_auth_failure` 限流按 worker 各算一份(实际速率≈worker 数×配置值),部署必须配置 Redis/Memcached 等共享 `CACHES` 后端并在文档说明,否则限流形同虚设 | DONE | +| T-403 | 部署 / 运行文档 | T-402 | 新环境可按文档运行;明确图片接口超时配置;上线前必须引用一次真实图片生成耗时来设置 Gunicorn `--timeout`、Nginx `proxy_read_timeout`、客户端 read timeout;若尚无真实耗时,部署文档必须显式标注图片同步风险未退,不得声称已验证;当前注册策略为免邮箱验证,注册登录不依赖 `DJANGO_EMAIL_BACKEND`,如后续启用密码找回/通知/邮箱验证再配置真实邮件服务;**生产静态文件 serving**:T-505 已把 Bootstrap/qrcode 自托管到 `apps/portal/static/`,但 `settings` 目前只有 `STATIC_URL`,`DEBUG=False` 下 Django 不发静态文件——必须配置 `STATIC_ROOT` + `collectstatic` + WhiteNoise 或 Nginx 托管 `/static/`,否则用户端 CSS 错版、充值二维码 404(P2-1 的国内可用性目标在生产失效);**DRF 限流依赖共享缓存**:多 Gunicorn worker 下默认 `LocMemCache` 是每进程的,会让 `generate`/`api_auth_failure` 限流按 worker 各算一份(实际速率≈worker 数×配置值),部署必须配置 Redis/Memcached 等共享 `CACHES` 后端并在文档说明,否则限流形同虚设 | DONE | ## Phase 6 · 增强(MVP 后) @@ -81,8 +81,8 @@ | --- | --- | --- | --- | --- | | T-601 | 可用别名发现(portal 别名页 + `GET /api/v1/models`) | T-202, T-301, T-503 | 让调用方能自助发现「能填哪些 `model` 能力别名」。① 新增 `GET /api/v1/models`(**API Key 鉴权,继承 `ExternalApiView`,只复用认证失败限流;不要占用生成接口的 `GenerateRateThrottle` 额度**),仅返回 `ModelAlias.is_active=True` 且 `ai_model.is_active=True` 的可调用别名;② 响应每项只含对外安全字段:`alias`、`operation_type`、`capabilities`、`requires_image`(按 `api_type`/provider 规则推导,如 `images_edits=true`)、`pricing_status`、`prices[{resolution, points_cost}]`,缺 `PricingRule` 时标「暂未定价」而非报错;③ **实现时不得调用 `AiModel.to_resolved_model()` 或任何会解密 provider key 的路径**,列表接口不依赖 `AI_KEY_ENCRYPTION_KEY`,也不读取/输出 `api_key_encrypted`;④ **不暴露具体 SKU/模型名/url/key/provider 原始配置**,测试必须断言响应不含 `ai_model.model`、`ai_model.url`、`api_key`、`api_key_encrypted`、`extra_body` 等内部字段;⑤ portal 加只读「可用模型」页(session 鉴权),同字段、人类可读、单价以点数展示,并在导航中加入入口;⑥ 含测试:API Key 成功返回结构、缺/无效 Key 拒绝、Web session 不能调用外部 API、portal 未登录跳转、页面不泄露 SKU/URL/Key、缺定价别名显示暂未定价。**标注**:未来接 `account_alias_permission`(见 Backlog)后,本清单改为「只列该用户被授权的别名」,接口与页面均按 `user` 过滤 | DONE | | T-602 | django-admin 中文化(第 1-3 层) | - | 让运营后台表名/分组/框架文字显示中文,**仅显示层、不改业务逻辑与 DB 结构**。**第 1 层**:`config/settings.py` 设 `LANGUAGE_CODE='zh-hans'` + `USE_I18N=True`,使 Django 自带 admin 界面与内置 `auth`(用户/组/权限)中文化(无迁移)。**第 2 层**:各 app `AppConfig.verbose_name` 设中文分组名(`users`/`billing`/`ai`/`api`/`portal`),无迁移。**第 3 层**:各模型 `Meta.verbose_name`/`verbose_name_plural` 设中文表名(`User`/`UserWallet`/`ApiKey`/`PointsLedger`/`CallRecord`/`PricingRule`/`ExchangeRate`/`RechargeOrder`/`AiModel`/`ModelAlias`/`AiConfigAuditLog`)。**不含第 4 层字段级 `verbose_name`(字段列名保持英文,见 T-603)**。第 3 层会产出 `AlterModelOptions` 等**无 DB 变更**迁移;验收:`makemigrations`(仅 options 变更)→ `migrate` → `check` 0 issues → `test` 全绿 → `/admin/` 目视中文化,并在 `../progress.md` 留证据 | DONE | -| T-603 | django-admin 中文化(第 4 层·字段级) | T-602 | 给各模型**字段**加中文 `verbose_name`,使 admin 列表/编辑页的字段标签显示中文,在 T-602(1-3 层)之上做。范围覆盖 `users`/`billing`/`ai`(及 portal 相关)模型的业务字段,例如 `points_balance`→点数余额、`key_prefix`→Key 前缀、`points_delta`→点数变动、`balance_after`→变动后余额、`amount_money`→金额、`exchange_rate`→汇率、`points_granted`→到账点数、`api_type`/`capabilities`/`is_active`/`created_at` 等;`admin.py` 里自定义显示方法(`@admin.display(description=...)`)同步中文。**仅显示层**:`verbose_name` 只改展示,**不改字段名、不改代码引用、英文字段名保持不变**;产出的是 `AlterField`(metadata-only,**无 DB schema 变更**)迁移。验收:`makemigrations`(仅 AlterField,无 schema 变更)→ `migrate` → `check` 0 issues → `test` 全绿 → `/admin/` 字段标签目视中文,并在 `../progress.md` 留证据。**当前阻塞**:代码与迁移已完成;`ACCOUNT_EMAIL_VERIFICATION="none"`(免邮箱验证)已定为**既定策略**(见 2026-07-06 决策),原假设 `mandatory` 的 2 条 portal 测试需**按新策略更新**(不再恢复 `mandatory`),由 **T-605** 处理;T-605 完成后本任务解封、重跑测试标 `DONE` | BLOCKED | -| T-605 | 落实「免邮箱验证」策略 | T-501 | 把 `ACCOUNT_EMAIL_VERIFICATION="none"`(注册即可用、不发验证邮件、邮箱仍必填且唯一)作为**既定策略**清理落地:① `config/settings.py` 把 `ACCOUNT_EMAIL_VERIFICATION = "none"#"mandatory"` 改为干净的 `"none"`(去行内注释),评估 `ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION` 是否仍需要;② 更新 `apps/portal/tests.py` 里假设 mandatory 的 2 条测试(signup 不再依赖验证邮件、未验证也可直接登录),改为断言「注册后可直接登录」;③ 同步全项目文档口径(`02-requirements`/`05-coding-rules`/`api`/`04-architecture`/`03-tech-stack`/`routes`/`env`/`deployment`/`00-ai-start-here` 中「邮箱验证」→「免邮箱验证,邮箱仍唯一」);④(可选)注册防刷改用图形验证码/限流补位。验收:`check`/`test` 全绿(含更新后的注册测试)并在 `../progress.md` 留证据;**完成后解封 T-603** | TODO | +| T-603 | django-admin 中文化(第 4 层·字段级) | T-602 | 给各模型**字段**加中文 `verbose_name`,使 admin 列表/编辑页的字段标签显示中文,在 T-602(1-3 层)之上做。范围覆盖 `users`/`billing`/`ai`(及 portal 相关)模型的业务字段,例如 `points_balance`→点数余额、`key_prefix`→Key 前缀、`points_delta`→点数变动、`balance_after`→变动后余额、`amount_money`→金额、`exchange_rate`→汇率、`points_granted`→到账点数、`api_type`/`capabilities`/`is_active`/`created_at` 等;`admin.py` 里自定义显示方法(`@admin.display(description=...)`)同步中文。**仅显示层**:`verbose_name` 只改展示,**不改字段名、不改代码引用、英文字段名保持不变**;产出的是 `AlterField`(metadata-only,**无 DB schema 变更**)迁移。验收:`makemigrations`(仅 AlterField,无 schema 变更)→ `migrate` → `check` 0 issues → `test` 全绿 → `/admin/` 字段标签目视中文,并在 `../progress.md` 留证据。T-605 已解除邮箱验证口径阻塞;当前待在稳定 MySQL 测试库上补跑完整验证后再标 `DONE` | TODO | +| T-605 | 落实「免邮箱验证」策略 | T-501 | 把 `ACCOUNT_EMAIL_VERIFICATION="none"`(注册即可用、不发验证邮件、邮箱仍必填且唯一)作为**既定策略**清理落地:① `config/settings.py` 把 `ACCOUNT_EMAIL_VERIFICATION = "none"#"mandatory"` 改为干净的 `"none"`(去行内注释),并把免验证下无实际意义的 `ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION` 设为 `False`;② 更新 `apps/portal/tests.py` 里假设 mandatory 的 2 条测试(signup 不再依赖验证邮件、未验证也可直接登录),改为断言「注册后可直接登录」;③ 同步全项目文档口径(`02-requirements`/`05-coding-rules`/`api`/`04-architecture`/`03-tech-stack`/`routes`/`env`/`deployment`/`00-ai-start-here` 中「邮箱验证」→「免邮箱验证,邮箱仍唯一」)。验收:`check` 通过,`apps.portal` 26 tests OK;全量 `manage.py test` 已尝试,跑到 95/131 后因远程 MySQL 连接超时失败(WinError 10051/10060),非本任务断言失败,详见 `../progress.md` | DONE | | T-604 | 中文敏感词本地过滤(本地 keyword provider) | T-302, T-401 | 按 [`moderation.md`](moderation.md) 实施。**范围收紧**:T-604 只做输入 prompt 的本地敏感词快筛,不做云内容安全、不做输出审核、不做图片审核。**关键时序**:serializer 后先审 prompt,命中即 `400 content_blocked`;不得先下载 `image_url`,不得预扣点,不写 `CallRecord` / `PointsLedger`,不调上游。**核心实现**:新增 `apps.moderation`、`SensitiveWord` 模型/admin/迁移、keyword provider、归一化管线、Aho-Corasick matcher;`ahocorapy` 作为候选依赖,编码前必须验证 PyPI 可用性和 API 形状。**缓存**:matcher 进程内缓存,词库变更用共享 cache 版本号失效,不能只靠 `post_save` signal;生产依赖共享 cache。**配置**:`MODERATION_ENABLED=false` 默认 no-op;启用时 `MODERATION_PROVIDER=keyword`;MVP `SensitiveWord.action` 只支持 `block`。**验收**:`check`/`test` 全绿,覆盖 no-op、命中拦截且不扣点/不建记录/不调上游/不下载图片、归一化防绕过、词库变更后 matcher 重建;真实词库数据不进仓库,`__pycache__` 不进 Git | TODO | ## 里程碑 @@ -104,6 +104,6 @@ - 退款对账自动化、API Key 轮换、Provider key 轮换(MultiFernet / 双 key 迁移)、限流细化。 - 用户端 API Key 数量治理:限制每用户 active Key 数量(如 10 个)、提供轮换与过期策略,避免无限建 Key。 - 充值下单风控:限制每用户 pending 订单数量或创建频率,避免反复点击刷出大量未支付订单并放大支付网关调用。 -- 用户端登录与凭证安全复核:评估 `ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION=True` 是否适合生产;评估新 API Key 明文短暂进入 session 的替代方案。 +- 用户端登录与凭证安全复核:评估新 API Key 明文短暂进入 session 的替代方案。 - allauth `account.EmailAddress` 的 MySQL `models.W036` 条件唯一约束警告为已知 cosmetic 警告;邮箱唯一性由 `user.email` 约束承担,后续升级 allauth / 数据库兼容性时复核。 - **SSRF DNS rebinding 加固(`image_url` 下载)**:当前 `download_image_input` 校验时 `getaddrinfo` 解析一次、`requests.get` 又独立解析一次,两次之间存在 TOCTOU(恶意 DNS 可先返公网 IP 过校验、再返内网 IP 供实际请求)。彻底堵需把连接**钉死到已校验的 IP**(自定义 `HTTPAdapter`/`connection` 连已验证 IP + 保留 Host 头做 TLS/路由),对每次重定向同样处理。属安全深水区,规模化/公网大流量前处理;上线前安全检查应至少标注此残留。 diff --git a/docs/current-state.md b/docs/current-state.md index efc7f03..f71f408 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -12,12 +12,12 @@ ## 当前快照 - 日期:2026-07-06 -- 阶段:Phase 6 增强(MVP 后);Phase 3 对外 API 与充值已完成到 T-306,Phase 4 用户端 T-501 注册 / 登录(allauth)、T-502 API Key 自助管理页、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化已完成,Phase 5 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档已完成,Phase 6 T-601 可用别名发现、T-602 django-admin 中文化第 1-3 层已完成;T-603 django-admin 字段级中文化代码与迁移已完成,但完整测试被工作区既有邮箱验证配置改动阻塞,暂记 `BLOCKED` +- 阶段:Phase 6 增强(MVP 后);Phase 3 对外 API 与充值已完成到 T-306,Phase 4 用户端 T-501 注册 / 登录(allauth)、T-502 API Key 自助管理页、T-503 个人中心 / 记录页、T-504 充值页与 T-505 用户端审核优化已完成,Phase 5 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档已完成,Phase 6 T-601 可用别名发现、T-602 django-admin 中文化第 1-3 层、T-605 免邮箱验证策略落地已完成;T-603 django-admin 字段级中文化代码与迁移已完成,待稳定 MySQL 测试库补跑完整验证后收尾 - 技术栈:系统 Python 3.12.3 + Django 5.2.15 + DRF 3.16.1 + django-allauth 65.18.0 + PyMySQL 1.1.3 + cryptography 49.0.0 + requests 2.34.2 + wechatpayv3 2.0.2 + python-alipay-sdk 3.4.0 + django-admin;MySQL 8.4 已接入 settings,并支持 `MYSQL_CONNECT_TIMEOUT` / `MYSQL_READ_TIMEOUT` / `MYSQL_WRITE_TIMEOUT`;用户端已用 Django 模板 SSR + Bootstrap + allauth 落地注册登录;生产部署口径为 VPS / 宝塔 + Nginx + Gunicorn(gthread) + systemd;详见 `03-tech-stack.md` 与 `deployment.md` - 生产代码:已有最小 Django 工程骨架:`manage.py`、`config/`;T-002 已创建 `apps/users|portal|billing|ai|api`;T-003 已把自定义 `User` 注册进 django-admin;T-004 已完成 email 唯一性、init 版本断言、app 顺序、`.env.example` 与 `pyproject.toml`;T-101 已新增 `apps/ai/providers/`(Provider 接口、注册表、chat/gemini/images/images_edits 适配器);T-102 已新增 `AiModel` / `ModelAlias`、Fernet 加密密钥存储、别名解析、admin 配置页、`import_ai_models` 导入命令;T-103 已新增 `AiConfigAuditLog` 审计表、admin 只读页面和后台保存/删除审计 hook;T-104/T-105 已完成录制 title/image smoke 与审核修补;T-201 已新增 `UserWallet` / `ApiKey`、`PointsLedger` / `CallRecord`、对应 admin 与迁移;T-202 已新增 `PricingRule` / `ExchangeRate`、`apps.billing.pricing` 计费计算函数、admin 配置页与迁移;T-203 已新增 `apps.billing.services`,实现并发安全预扣、成功确认与幂等失败退点;T-204 已新增 `billing.0003_pointsledger_unique_ledger_change_type_per_call`,用 MySQL 可落地的 `ref_call + change_type` 复合唯一约束兜底防重复 refund;T-301 已新增 `apps.api.authentication.ApiKeyAuthentication` 与 `ExternalApiView`;T-302 已新增生成接口编排、序列化器、图片本地存储和 `/api/v1/generate/title|image` 路由;T-303 已新增 `apps.billing.services.get_balance_snapshot()` 与 `/api/v1/balance` 余额查询接口;T-304 已新增 `RechargeOrder`、充值回调验签适配器、幂等入账服务、微信/支付宝回调路由与迁移 `billing.0004_rechargeorder_and_more`;T-305 已新增 `create_recharge_order()`、微信/支付宝扫码下单 mock/SDK 入口、`/api/v1/recharge/create` 与 `/api/v1/recharge/status`;T-306 已新增 `apps.api.throttles`、`apps.api.exceptions`、`REST_FRAMEWORK` 安全默认认证、生成/认证失败限流、`image_url` SSRF 防护与响应大小上限、充值单笔金额上限;T-501 已接入 allauth,新增 portal 路由、注册适配器、登录/注册/登出模板和最小 dashboard,注册成功创建 0 点钱包且不写赠点流水;T-502 已新增 `/apikeys`、API Key 创建表单、列表页和删除(吊销)动作,生成后明文只显示一次,列表只显示 prefix;T-503 已扩展 `/dashboard` 为个人中心汇总,并新增 `/records/recharge` 充值记录与 `/records/usage` 消费记录,只读展示当前用户数据;T-504 已新增 `/recharge` 页面、`RechargeCreateForm`、充值导航入口和轮询脚本,页面创建 pending 订单、展示二维码票据、轮询 `/api/v1/recharge/status`,订单 paid 后刷新余额;T-505 已把 Bootstrap 5 CSS 与 qrcode.js vendoring 到 `apps/portal/static/portal/vendor/`,页面不再依赖 jsdelivr,并把充值记录 / 消费记录改为 Django `Paginator` 分页;T-401 已新增 `adjust_wallet_points()` 手工调点服务、钱包 admin 专用调点表单与模板,后台可管理/检索用户、钱包、API Key(脱敏)、计费规则、汇率、充值订单、点数流水和调用记录,流水/订单/调用记录保持只读;T-402 已新增 `docs/mvp-acceptance.md`,按 P0 验收矩阵记录 MVP 完整验收结论、测试证据和已知限制;T-403 已新增 `docs/deployment.md` 与 `requirements-production.txt`,并在 settings 中补齐 `STATIC_ROOT`、`CSRF_TRUSTED_ORIGINS`、共享 `CACHES`、HTTPS cookie、proxy SSL 与 HSTS 环境变量支持;T-601 已新增 `apps.ai.catalog.get_public_model_catalog()`、`GET /api/v1/models` 与 portal `/models` 只读页面,只展示 active 可调用别名、能力、是否需要原图和点数单价,不解密 provider key,不暴露底层 SKU / URL / key / `extra_body` - 用户端导航:顶部导航 active 状态已修复,`portal/base.html` 基于 `request.resolver_match.url_name` 高亮当前页面入口,并用 `aria-current="page"` 标记;「充值」不再在非充值页固定深色高亮。 - 测试:T-601 已验证:`.\init.ps1` 通过(Python 3.12.3,依赖已满足,`manage.py check` 0 issues,打印启动命令);`py -3.12 -m py_compile apps\ai\catalog.py apps\api\views.py apps\api\urls.py apps\api\tests.py apps\portal\views.py apps\portal\urls.py apps\portal\tests.py` 通过;`py -3.12 manage.py check` 通过;`py -3.12 manage.py makemigrations --check --dry-run` 无变化;`git diff --check` 通过;`py -3.12 manage.py test apps.api.tests.ModelsCatalogApiTests apps.portal.tests.PortalAccountFlowTests.test_models_page_requires_session_login apps.portal.tests.PortalAccountFlowTests.test_models_page_lists_public_aliases_prices_and_unpriced_state --keepdb --noinput --verbosity 2` 首次运行时 3 条 API 测试已通过,portal `setUpClass` 阶段因远程 MySQL 连接 43.128.3.240 超时中断;随后 `Test-NetConnection 43.128.3.240 -Port 3306` 显示 `TcpTestSucceeded=True`,单独重跑两条 portal 测试通过,2 tests OK。T-403 已验证:`./init.ps1` 开工前通过;`py -3.12 manage.py check` 通过;`py -3.12 manage.py makemigrations --check --dry-run` 无变化;`py -3.12 manage.py findstatic portal/vendor/bootstrap/bootstrap.min.css portal/vendor/qrcode/qrcode.js --verbosity 1` 找到两个本地 static 文件;`py -3.12 manage.py check --deploy` 在当前开发 `.env` 下仅报预期安全配置警告,临时注入生产型安全环境变量(含 HSTS includeSubDomains/preload)后通过,0 issues;`py -3.12 manage.py collectstatic --dry-run --noinput` 通过,预期收集 169 个 static 文件;`py -3.12 manage.py createcachetable --dry-run cmhub_cache` 通过,输出 MySQL cache 表 DDL;`py -3.12 -m compileall config` 通过;尝试 `py -3.12 manage.py test apps.api --noinput --keepdb --verbosity 2` 时 25 条用例已通过,`GenerateApiTests` 14 条因远程 MySQL 连接重置/事务中断被记 ERROR,随后 `Test-NetConnection 43.128.3.240 -Port 3306` 端口可达,单独重跑 `py -3.12 manage.py test apps.api.tests.GenerateApiTests --noinput --keepdb --verbosity 2` 通过,14 tests OK。测试/迁移阶段仍有 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的 `models.W036` 警告;本项目用户账本邮箱唯一性由 `user.email` 唯一约束承担。 -- 最新验证:T-603 字段级中文化已验证 `py -3.12 -m py_compile apps\users\models.py apps\users\admin.py apps\billing\models.py apps\billing\admin.py apps\ai\models.py apps\ai\admin.py` 通过;`py -3.12 manage.py makemigrations` 仅生成 `users.0005` / `billing.0006` / `ai.0004` 三个 `AlterField` 迁移;`py -3.12 manage.py sqlmigrate users 0005`、`billing 0006`、`ai 0004` 均为 `(no-op)`;`py -3.12 manage.py migrate` 应用成功,仅出现既有 allauth MySQL `models.W036` 警告;`py -3.12 manage.py makemigrations --check --dry-run` 通过,No changes detected;`py -3.12 manage.py check` 通过;`git diff --check` 通过,仅 Windows CRLF 提示;`py -3.12 manage.py test --keepdb --noinput --verbosity 2` 跑 125 条,123 条通过,2 条失败,失败原因是工作区既有 `config/settings.py` 把 `ACCOUNT_EMAIL_VERIFICATION` 改为 `none`,导致 `test_signup_creates_unverified_user_wallet_with_zero_points_and_no_ledger` 收不到验证邮件、`test_unverified_email_cannot_establish_login_session` 未验证用户仍建立登录 session。 +- 最新验证:T-605 免邮箱验证策略已验证 `py -3.12 -m py_compile config\settings.py apps\portal\tests.py` 通过;`py -3.12 manage.py check` 通过,0 issues;`py -3.12 manage.py makemigrations --check --dry-run` 通过,No changes detected;更新后的注册 / 登录目标测试 3 tests OK;`py -3.12 manage.py test apps.portal --keepdb --noinput --verbosity 2` 通过,26 tests OK;`git diff --check` 通过,仅 Windows CRLF 提示。全量 `py -3.12 manage.py test --keepdb --noinput --verbosity 2` 已尝试,发现 131 tests,跑到 95 tests 后远程 MySQL `43.128.3.240` 出现连接超时 / WinError 10051,最终 22 errors;随后 `Test-NetConnection 43.128.3.240 -Port 3306` 显示 `TcpTestSucceeded=True`,单独复跑失败起点的 billing 分组时 17 条业务用例 OK,并发扣点用例本体打印 `ok` 后在测试库 flush 阶段再次因 MySQL 连接超时 / WinError 10060 记 ERROR。结论:T-605 相关测试已通过,全量单次绿受远程 MySQL 稳定性阻塞。 - 线上热修验证:`/admin/billing/exchangerate/` 500 根因是 `ExchangeRateAdmin.date_hierarchy` 触发 MySQL `CONVERT_TZ`,而生产 MySQL 未加载时区表,Django 抛 `ValueError: Database returned an invalid datetime value`;已移除 `apps.billing.admin` 中 4 个 DateTime `date_hierarchy`,保留 `list_filter` 日期筛选。验证 `py -3.12 -m py_compile apps\billing\admin.py apps\billing\tests.py`、`py -3.12 manage.py check`、`py -3.12 manage.py test apps.billing.tests.BillingAdminTests.test_exchange_rate_changelist_renders_when_rates_exist --keepdb --noinput --verbosity 2` 均通过。 - Mock 支付提示:`PAYMENT_CALLBACK_MODE=mock` 时,用户端 `/recharge` 的待支付订单区域会显示醒目测试模式提示,说明二维码只用于联调订单创建、页面渲染和状态轮询,不能使用微信或支付宝真实付款;`sdk` 模式不显示该提示。验证 `py -3.12 -m py_compile apps\portal\views.py apps\portal\tests.py`、`py -3.12 manage.py check`、`py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests.test_recharge_page_post_creates_pending_order_without_crediting_wallet_or_ledger apps.portal.tests.PortalAccountFlowTests.test_recharge_page_does_not_show_mock_notice_in_sdk_mode --keepdb --noinput --verbosity 2` 均通过。 - 微信正式下单热修:线上微信 Native 下单失败根因是 `wechatpayv3` SDK 需要显式 `pay_type=WeChatPayType.NATIVE`,且成功响应为 `(status_code, json_string)` tuple 而非 dict;已兼容 tuple/json 响应并覆盖下单与主动查单。线上用修复后的 `_create_wechat_payment_order_with_sdk()` 创建 1 分诊断预支付请求,确认返回 `weixin://wxpay/bizpayurl` 票据。另:pending 订单主动查单异常不再让 `/api/v1/recharge/status` 返回 500,而是保持当前订单状态返回 200。 @@ -26,8 +26,8 @@ - 标准启动路径:Windows 用 `./init.ps1`;Unix/WSL 用 `./init.sh` - 标准验证路径:Windows 用 `py -3.12 manage.py check` / `py -3.12 manage.py test` - 设计基线:**自助用户端 + 对外 API + 运营后台**三合一单体;用户模型 `User`(auth)/`UserWallet`(点数,锁 wallet 扣点)/`ApiKey`(1:N,哈希存储);对外两接口 + **能力别名 + Provider 适配器**(可插拔供应商);自助扫码充值;注册不送点数。详见 `04-architecture.md` 与 2026-06-29 / 2026-07-01 的 `progress.md` 决策 -- 配置基线:运行环境变量集中见 `docs/env.md`;真实密钥/支付凭证不得写入代码或文档样例。allauth 邮箱验证发信由 `DJANGO_EMAIL_BACKEND` / `DJANGO_DEFAULT_FROM_EMAIL` 控制,本地默认 console backend,生产需配置真实邮件服务。充值订单在创建时锁定汇率与预计点数,回调入账使用订单值,不按新汇率重算。支付回调与下单由 `PAYMENT_CALLBACK_MODE` 控制:本地/测试可用 HMAC `mock`,生产应为 `sdk`;二维码本地有效期提示由 `PAYMENT_QR_EXPIRES_MINUTES` 控制。T-306 起对外 API 安全配置包含 `API_GENERATE_THROTTLE_RATE`、`API_AUTH_FAILURE_THROTTLE_RATE`、`IMAGE_URL_MAX_BYTES`、`IMAGE_URL_MAX_REDIRECTS`、`IMAGE_URL_CONNECT_TIMEOUT_SECONDS`、`IMAGE_URL_READ_TIMEOUT_SECONDS`、`RECHARGE_MAX_AMOUNT_CNY`。T-403 起生产静态、共享 cache 与 HTTPS 安全配置包含 `STATIC_URL`、`STATIC_ROOT`、`DJANGO_CACHE_BACKEND`、`DJANGO_CACHE_LOCATION`、`DJANGO_CSRF_TRUSTED_ORIGINS`、`DJANGO_SESSION_COOKIE_SECURE`、`DJANGO_CSRF_COOKIE_SECURE`、`DJANGO_SECURE_SSL_REDIRECT`、`DJANGO_SECURE_PROXY_SSL_HEADER`、`DJANGO_SECURE_HSTS_SECONDS`。 -- 当前 blocker:T-603 完整测试当前被工作区既有 `config/settings.py` 改动阻塞:`ACCOUNT_EMAIL_VERIFICATION` 从 `mandatory` 改为 `none`,导致注册不发验证邮件、未验证用户也能登录,2 条 portal 邮箱验证测试失败;需恢复 `mandatory` 后重跑失败用例和完整测试,再把 T-603 标为 `DONE`。另:远程 MySQL 连接仍可能很慢或间歇超时,完整测试需预留较长时间并优先用 `--keepdb` 串行跑,必要时改用更稳定的测试库。微信正式下单已能返回二维码,但线上微信回调曾出现 `PaymentVerificationError`,仍需单独修复并完成“付款后自动入账”闭环验收;支付宝恢复依赖开放平台把 `43.128.3.240` 加入可信 IP。真实 AI 上游 smoke 需要先配置 `AI_KEY_ENCRYPTION_KEY` 并导入 AiModel/ModelAlias。图片同步真实耗时风险仍未退,T-403 已在 `deployment.md` 明确上线前必须记录真实图片 smoke 耗时;未跑通前只能按保守超时内测,不能声称已验证。 +- 配置基线:运行环境变量集中见 `docs/env.md`;真实密钥/支付凭证不得写入代码或文档样例。用户端注册策略固定为 `ACCOUNT_EMAIL_VERIFICATION="none"`:免邮箱验证、注册即可用、邮箱仍必填且唯一;`DJANGO_EMAIL_BACKEND` / `DJANGO_DEFAULT_FROM_EMAIL` 仅用于后续密码找回、通知或恢复邮箱验证等邮件能力,不作为当前注册登录前置条件。充值订单在创建时锁定汇率与预计点数,回调入账使用订单值,不按新汇率重算。支付回调与下单由 `PAYMENT_CALLBACK_MODE` 控制:本地/测试可用 HMAC `mock`,生产应为 `sdk`;二维码本地有效期提示由 `PAYMENT_QR_EXPIRES_MINUTES` 控制。T-306 起对外 API 安全配置包含 `API_GENERATE_THROTTLE_RATE`、`API_AUTH_FAILURE_THROTTLE_RATE`、`IMAGE_URL_MAX_BYTES`、`IMAGE_URL_MAX_REDIRECTS`、`IMAGE_URL_CONNECT_TIMEOUT_SECONDS`、`IMAGE_URL_READ_TIMEOUT_SECONDS`、`RECHARGE_MAX_AMOUNT_CNY`。T-403 起生产静态、共享 cache 与 HTTPS 安全配置包含 `STATIC_URL`、`STATIC_ROOT`、`DJANGO_CACHE_BACKEND`、`DJANGO_CACHE_LOCATION`、`DJANGO_CSRF_TRUSTED_ORIGINS`、`DJANGO_SESSION_COOKIE_SECURE`、`DJANGO_CSRF_COOKIE_SECURE`、`DJANGO_SECURE_SSL_REDIRECT`、`DJANGO_SECURE_PROXY_SSL_HEADER`、`DJANGO_SECURE_HSTS_SECONDS`。 +- 当前 blocker:T-603 不再受邮箱验证口径阻塞;剩余阻塞是远程 MySQL 测试库长跑稳定性,完整测试需预留较长时间并优先用 `--keepdb` 串行跑,必要时改用更稳定的测试库。微信正式下单已能返回二维码,但线上微信回调曾出现 `PaymentVerificationError`,仍需单独修复并完成“付款后自动入账”闭环验收;支付宝恢复依赖开放平台把 `43.128.3.240` 加入可信 IP。真实 AI 上游 smoke 需要先配置 `AI_KEY_ENCRYPTION_KEY` 并导入 AiModel/ModelAlias。图片同步真实耗时风险仍未退,T-403 已在 `deployment.md` 明确上线前必须记录真实图片 smoke 耗时;未跑通前只能按保守超时内测,不能声称已验证。 ## 当前目录要点 @@ -47,11 +47,11 @@ 任务状态以 [`06-tasks.md`](06-tasks.md) 为准,历史执行记录见 [`../progress.md`](../progress.md)。 -- 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试;T-004 Phase 0 骨架审核修补;T-101 Provider 适配器层 + 移植 cmbot 调用;T-102 AiModel + ModelAlias 模型 + 别名解析;T-103 配置变更审计;T-104 跑通一次录制标题生成;T-105 Phase 1 AI 层审核修补;T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型;T-202 PricingRule / ExchangeRate 模型 + 计费计算;T-203 并发安全扣点 / 退点;T-204 Phase 2 计费核心审核加固;T-301 API Key 鉴权;T-302 生成标题 / 图片接口;T-303 余额查询接口;T-304 充值回调;T-305 扫码充值下单 + 轮询;T-306 Phase 3 对外 API 安全加固;T-501 注册 / 登录(allauth);T-502 API Key 自助管理页;T-503 个人中心 / 记录页;T-504 充值页(扫码 + 轮询到账);T-505 Phase 4 用户端审核优化;T-401 运营后台完善;T-402 完整验收 MVP;T-403 部署 / 运行文档;T-601 可用别名发现;T-602 django-admin 中文化(第 1-3 层)。 +- 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试;T-004 Phase 0 骨架审核修补;T-101 Provider 适配器层 + 移植 cmbot 调用;T-102 AiModel + ModelAlias 模型 + 别名解析;T-103 配置变更审计;T-104 跑通一次录制标题生成;T-105 Phase 1 AI 层审核修补;T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型;T-202 PricingRule / ExchangeRate 模型 + 计费计算;T-203 并发安全扣点 / 退点;T-204 Phase 2 计费核心审核加固;T-301 API Key 鉴权;T-302 生成标题 / 图片接口;T-303 余额查询接口;T-304 充值回调;T-305 扫码充值下单 + 轮询;T-306 Phase 3 对外 API 安全加固;T-501 注册 / 登录(allauth);T-502 API Key 自助管理页;T-503 个人中心 / 记录页;T-504 充值页(扫码 + 轮询到账);T-505 Phase 4 用户端审核优化;T-401 运营后台完善;T-402 完整验收 MVP;T-403 部署 / 运行文档;T-601 可用别名发现;T-602 django-admin 中文化(第 1-3 层);T-605 免邮箱验证策略落地。 - 正在进行:无。 -- 待开始:暂无已拆任务;T-603 解除阻塞并完成后,可继续处理真实邮件、支付、AI 模型导入与真实图片耗时验证,或从 Backlog 拆新任务。 -- 当前 blocker:T-603 字段级中文化的代码/迁移已完成,验证被既有 `ACCOUNT_EMAIL_VERIFICATION="none"` 配置改动阻塞;恢复为 `mandatory` 后需重跑失败用例和完整测试。远程 MySQL 连接当前不稳定或很慢,完整测试单次全绿可能需要稳定测试库;支付商户真实密钥/证书与生产 SDK 依赖仍待提供;真实 AI 上游 smoke 仍需配置密钥和模型数据后执行。 -- 下一个可领取任务:先解除 T-603 阻塞并补跑测试,之后暂无后续已拆 `TODO`。 +- 待开始:T-603 待在稳定 MySQL 测试库上补跑完整验证;之后可继续处理真实支付、AI 模型导入与真实图片耗时验证,或从 Backlog 拆新任务。 +- 当前 blocker:T-603 的字段级中文化代码/迁移已完成,邮箱验证口径已由 T-605 解封;当前只剩远程 MySQL 长跑稳定性阻塞完整测试单次全绿。支付商户真实密钥/证书与生产 SDK 依赖仍待提供;真实 AI 上游 smoke 仍需配置密钥和模型数据后执行。 +- 下一个可领取任务:T-603 补跑完整验证并根据结果收尾。 ## 当前可运行内容 @@ -107,15 +107,15 @@ gunicorn config.wsgi:application --bind 127.0.0.1:8002 --workers 1 --worker-clas - `GET /records/recharge` - `GET /records/usage` -当前骨架可运行。T-002 已在首次迁移前创建自定义 User,并按 `env.md` 接入 MySQL 8.4 / utf8mb4;远程 MySQL 已完成 Django 初始迁移。T-003 已接通 django-admin,测试可创建/销毁 `test_cmhub` 测试库;当前远程 MySQL 对频繁建库/销库仍可能间歇超时,必要时用 `--keepdb` 且串行跑测试。T-004 已应用 `users.0002_alter_user_email`,`user.email` 已有唯一索引。T-101 的 AI provider 层只做 HTTP 调用与响应解析;T-102 已把 provider 运行配置接到数据库 `AiModel` / `ModelAlias`,`resolve_alias()` 每次查当前 active 配置并按 `text` / `image` 能力校验。T-103 已补 `AiConfigAuditLog`,admin 保存/删除 `AiModel` / `ModelAlias` 时记录 actor、action、target、changed_fields、changes、created_at,密钥只记录 empty/set 状态。T-104/T-105 已用临时回滚配置跑通录制标题和录制图片生成。T-201 已落地钱包、API Key、点数流水和调用记录:API Key 明文只在创建 helper 返回,库内只存 hash/prefix;CallRecord 只存 `result_ref`/`result_summary`,没有 provider raw 字段。T-202 已落地 `PricingRule` / `ExchangeRate`:计费按 `operation_type + alias + resolution` 查 active 规则,优先精确分辨率,再回退默认价;缺规则抛 `NoPricingRuleError(code="no_pricing_rule")`;金额换点数按当前 active 汇率向下取整。T-203 已落地 `precharge_call()` / `mark_call_success()` / `refund_call_points()`:预扣锁钱包行,余额不足不写调用/流水;失败退点锁调用记录并幂等写 refund 流水。T-204 已完成复合唯一约束加固,并取得一次完整 `manage.py test` 单次全绿。T-301 已落地 `Authorization: Bearer ` 鉴权:成功后 `request.user` 为所属用户、`request.auth` 为 `ApiKey`,缺失/无效 Key 返回 401,用户或 Key 禁用返回 403,外部 API 不接受 Web session。T-302 已落地生成接口:请求别名解析后按规则计费,预扣成功才调用 Provider,成功确认调用记录,`AiProviderError` / `AiCapabilityError` 等失败路径会退点;图片结果保存到本地 media 并返回 URL。T-303 已落地余额查询接口:`GET /api/v1/balance` 继承外部 API Key 鉴权,读取 billing 余额快照并返回 `user` 与 `points_balance`,测试覆盖余额与流水累加一致。T-304 已落地充值回调:`RechargeOrder` 保存下单锁定的金额/汇率/点数,微信/支付宝回调先验签再按订单幂等入账,重复回调不重复加点,金额不一致不入账;主动查单兜底可调用 `query_and_apply_recharge_payment(order_no, query_func)` 复用同一入账路径。T-305 已落地扫码下单与轮询:用户端 session 登录后可 `POST /api/v1/recharge/create` 创建 pending 订单并拿到 mock/SDK 二维码票据,`GET /api/v1/recharge/status` 只返回本人订单并在 pending 时尝试主动查单补入账;API Key 不能调用这两个用户端接口。T-306 已落地对外 API 安全加固:`image_url` 下载在扣点前做协议白名单、公网地址校验、重定向逐跳校验和响应大小上限;DRF 全局默认不再隐式启用 Session/Basic;生成接口按 Key 限流,认证失败按 IP 限流;充值下单有单笔金额上限。T-501 已落地 allauth 注册 / 登录:`ACCOUNT_EMAIL_VERIFICATION="mandatory"`,注册成功创建 0 点钱包、不写点数流水;未验证邮箱不能建立登录 session。T-502 已落地 API Key 自助管理:`/apikeys` 登录访问,生成后完整明文只显示一次,列表只显示 prefix,不显示 hash 或历史明文;删除为吊销 `revoked`,吊销后外部 API 返回 403。T-503 已落地个人中心与记录页:`/dashboard` 展示剩余点数、充值总额、入账点数、净消耗点数和最近记录;`/records/recharge` 展示当前用户充值订单;`/records/usage` 展示当前用户 consume/refund 点数流水并关联调用信息;所有页面均只读且只查本人。T-504 已落地充值页:`/recharge` GET 展示余额、充值表单、当前订单和最近充值,POST 创建 pending 订单并展示二维码票据,浏览器轮询 `/api/v1/recharge/status`,paid 后刷新页面重新读取余额;页面不直接写钱包或流水。T-401 已落地运营后台完善:用户列表显示钱包余额,钱包余额只读且通过专用表单手工调点,调点必须填原因、非 0、不得扣成负数,并经 `adjust_wallet_points()` 锁钱包写 `PointsLedger(adjust)`;API Key admin 只展示 prefix 和 hash 摘要,不回显明文或完整 hash;订单、流水、调用记录继续只读并增强检索。T-402 已完成 MVP P0 验收并新增 `docs/mvp-acceptance.md`。T-403 已完成部署 / 运行文档,生产按 `deployment.md` 执行,并已补 settings 对生产静态目录、共享 cache、CSRF trusted origins、HTTPS cookie/proxy/HSTS 的环境变量支持。T-601 已落地可用别名发现:`GET /api/v1/models` 用 API Key 鉴权返回公开别名目录,`/models` 用 session 展示只读「可用模型」页;两者均不解密 provider key,不输出底层 SKU、URL、key 或 `extra_body`。真实上游生成未执行,原因是当前环境未配置 `AI_KEY_ENCRYPTION_KEY` 且数据库没有 AiModel/ModelAlias;后续配置后可用 `import_ai_models` 导入,再通过接口跑真实标题/图片并记录图片耗时。 +当前骨架可运行。T-002 已在首次迁移前创建自定义 User,并按 `env.md` 接入 MySQL 8.4 / utf8mb4;远程 MySQL 已完成 Django 初始迁移。T-003 已接通 django-admin,测试可创建/销毁 `test_cmhub` 测试库;当前远程 MySQL 对频繁建库/销库仍可能间歇超时,必要时用 `--keepdb` 且串行跑测试。T-004 已应用 `users.0002_alter_user_email`,`user.email` 已有唯一索引。T-101 的 AI provider 层只做 HTTP 调用与响应解析;T-102 已把 provider 运行配置接到数据库 `AiModel` / `ModelAlias`,`resolve_alias()` 每次查当前 active 配置并按 `text` / `image` 能力校验。T-103 已补 `AiConfigAuditLog`,admin 保存/删除 `AiModel` / `ModelAlias` 时记录 actor、action、target、changed_fields、changes、created_at,密钥只记录 empty/set 状态。T-104/T-105 已用临时回滚配置跑通录制标题和录制图片生成。T-201 已落地钱包、API Key、点数流水和调用记录:API Key 明文只在创建 helper 返回,库内只存 hash/prefix;CallRecord 只存 `result_ref`/`result_summary`,没有 provider raw 字段。T-202 已落地 `PricingRule` / `ExchangeRate`:计费按 `operation_type + alias + resolution` 查 active 规则,优先精确分辨率,再回退默认价;缺规则抛 `NoPricingRuleError(code="no_pricing_rule")`;金额换点数按当前 active 汇率向下取整。T-203 已落地 `precharge_call()` / `mark_call_success()` / `refund_call_points()`:预扣锁钱包行,余额不足不写调用/流水;失败退点锁调用记录并幂等写 refund 流水。T-204 已完成复合唯一约束加固,并取得一次完整 `manage.py test` 单次全绿。T-301 已落地 `Authorization: Bearer ` 鉴权:成功后 `request.user` 为所属用户、`request.auth` 为 `ApiKey`,缺失/无效 Key 返回 401,用户或 Key 禁用返回 403,外部 API 不接受 Web session。T-302 已落地生成接口:请求别名解析后按规则计费,预扣成功才调用 Provider,成功确认调用记录,`AiProviderError` / `AiCapabilityError` 等失败路径会退点;图片结果保存到本地 media 并返回 URL。T-303 已落地余额查询接口:`GET /api/v1/balance` 继承外部 API Key 鉴权,读取 billing 余额快照并返回 `user` 与 `points_balance`,测试覆盖余额与流水累加一致。T-304 已落地充值回调:`RechargeOrder` 保存下单锁定的金额/汇率/点数,微信/支付宝回调先验签再按订单幂等入账,重复回调不重复加点,金额不一致不入账;主动查单兜底可调用 `query_and_apply_recharge_payment(order_no, query_func)` 复用同一入账路径。T-305 已落地扫码下单与轮询:用户端 session 登录后可 `POST /api/v1/recharge/create` 创建 pending 订单并拿到 mock/SDK 二维码票据,`GET /api/v1/recharge/status` 只返回本人订单并在 pending 时尝试主动查单补入账;API Key 不能调用这两个用户端接口。T-306 已落地对外 API 安全加固:`image_url` 下载在扣点前做协议白名单、公网地址校验、重定向逐跳校验和响应大小上限;DRF 全局默认不再隐式启用 Session/Basic;生成接口按 Key 限流,认证失败按 IP 限流;充值下单有单笔金额上限。T-501/T-605 已落地 allauth 注册 / 登录:`ACCOUNT_EMAIL_VERIFICATION="none"`,免邮箱验证、注册即可用,邮箱仍必填且唯一;注册成功创建 0 点钱包、不写点数流水。T-502 已落地 API Key 自助管理:`/apikeys` 登录访问,生成后完整明文只显示一次,列表只显示 prefix,不显示 hash 或历史明文;删除为吊销 `revoked`,吊销后外部 API 返回 403。T-503 已落地个人中心与记录页:`/dashboard` 展示剩余点数、充值总额、入账点数、净消耗点数和最近记录;`/records/recharge` 展示当前用户充值订单;`/records/usage` 展示当前用户 consume/refund 点数流水并关联调用信息;所有页面均只读且只查本人。T-504 已落地充值页:`/recharge` GET 展示余额、充值表单、当前订单和最近充值,POST 创建 pending 订单并展示二维码票据,浏览器轮询 `/api/v1/recharge/status`,paid 后刷新页面重新读取余额;页面不直接写钱包或流水。T-401 已落地运营后台完善:用户列表显示钱包余额,钱包余额只读且通过专用表单手工调点,调点必须填原因、非 0、不得扣成负数,并经 `adjust_wallet_points()` 锁钱包写 `PointsLedger(adjust)`;API Key admin 只展示 prefix 和 hash 摘要,不回显明文或完整 hash;订单、流水、调用记录继续只读并增强检索。T-402 已完成 MVP P0 验收并新增 `docs/mvp-acceptance.md`。T-403 已完成部署 / 运行文档,生产按 `deployment.md` 执行,并已补 settings 对生产静态目录、共享 cache、CSRF trusted origins、HTTPS cookie/proxy/HSTS 的环境变量支持。T-601 已落地可用别名发现:`GET /api/v1/models` 用 API Key 鉴权返回公开别名目录,`/models` 用 session 展示只读「可用模型」页;两者均不解密 provider key,不输出底层 SKU、URL、key 或 `extra_body`。真实上游生成未执行,原因是当前环境未配置 `AI_KEY_ENCRYPTION_KEY` 且数据库没有 AiModel/ModelAlias;后续配置后可用 `import_ai_models` 导入,再通过接口跑真实标题/图片并记录图片耗时。 ## 开始编码前检查 1. 读仓库级 `AGENTS.md` / `CLAUDE.md`。 2. 读 `docs/00-ai-start-here.md`。 3. 读 `docs/05-coding-rules.md`(尤其第 8 节资金安全)。 -4. 在 `docs/06-tasks.md` 领取第一个 `TODO` 且依赖均 `DONE` 的任务;当前 T-603 为 `BLOCKED`。 -5. 先解除 T-603 的邮箱验证配置阻塞并补跑测试,再将任务状态改为 `DONE`。 +4. 在 `docs/06-tasks.md` 领取第一个 `TODO` 且依赖均 `DONE` 的任务;当前 T-603 为 `TODO`。 +5. 当前下一个任务是 T-603:在稳定 MySQL 测试库上补跑完整验证,再决定是否标 `DONE`。 ## 维护规则 diff --git a/docs/deployment.md b/docs/deployment.md index a46ed80..319bd57 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -89,7 +89,7 @@ DJANGO_ALLOWED_HOSTS=cmhub.example.com,127.0.0.1 DJANGO_CSRF_TRUSTED_ORIGINS=https://cmhub.example.com DJANGO_TIME_ZONE=Asia/Shanghai -DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +DJANGO_EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend DJANGO_DEFAULT_FROM_EMAIL=noreply@cmhub.example.com DJANGO_SESSION_COOKIE_SECURE=true DJANGO_CSRF_COOKIE_SECURE=true @@ -131,7 +131,7 @@ python3.12 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key 注意: - `AI_KEY_ENCRYPTION_KEY` 生产不可随意更换;更换会导致已加密的 `AiModel.api_key_encrypted` 无法解密。 -- 生产必须配置真实邮件服务,否则 allauth 邮箱验证邮件发不出,用户无法完成注册登录。 +- 当前注册策略为免邮箱验证,注册登录不依赖邮件服务;若后续启用密码找回、通知或恢复邮箱验证,再把 `DJANGO_EMAIL_BACKEND` 改为真实 SMTP / 邮件服务并配置 `DJANGO_DEFAULT_FROM_EMAIL`。 - `PAYMENT_CALLBACK_MODE=sdk` 必须配齐微信 / 支付宝商户配置;未配齐时先保持 `mock`。 - 宝塔 / Nginx 已强制 HTTPS 时,`DJANGO_SECURE_SSL_REDIRECT=false` 即可;全站 HTTPS 稳定后再把 `DJANGO_SECURE_HSTS_SECONDS` 调大,避免 HSTS 误锁域名。 @@ -291,7 +291,7 @@ python3.12 manage.py smoke_ai_generation image - `DJANGO_DEBUG=false`。 - `DJANGO_SECRET_KEY`、`AI_KEY_ENCRYPTION_KEY`、MySQL 密码、支付密钥均为生产值且未提交。 - `DJANGO_ALLOWED_HOSTS`、`DJANGO_CSRF_TRUSTED_ORIGINS`、微信 / 支付宝 `notify_url` 使用同一 HTTPS 域名。 -- 邮件后端可发送验证邮件。 +- 如启用密码找回、通知或邮箱验证等邮件能力,邮件后端可发送邮件;当前免邮箱验证策略不把邮件服务作为注册登录上线前置条件。 - HTTPS 已由宝塔 / Nginx 强制跳转;如由 Django 强制跳转则设置 `DJANGO_SECURE_SSL_REDIRECT=true`。HSTS 只在确认全站 HTTPS 后启用。 - `STATIC_ROOT` 已 `collectstatic`,Nginx 可访问 `/static/portal/vendor/bootstrap/bootstrap.min.css` 和 `/static/portal/vendor/qrcode/qrcode.js`。 - `MEDIA_ROOT` 或对象存储可访问生成图片 URL。 diff --git a/docs/env.md b/docs/env.md index 6f3f4cc..bdd4f8d 100644 --- a/docs/env.md +++ b/docs/env.md @@ -19,8 +19,8 @@ | `DJANGO_ALLOWED_HOSTS` | 是 | `cmhub.example.com,127.0.0.1` | 逗号分隔 | | `DJANGO_CSRF_TRUSTED_ORIGINS` | 生产是 | `https://cmhub.example.com` | 用户端表单、admin、充值页需要 | | `DJANGO_TIME_ZONE` | 否 | `Asia/Shanghai` | 默认按中国业务时区 | -| `DJANGO_EMAIL_BACKEND` | 否 | `django.core.mail.backends.console.EmailBackend` | allauth 注册邮箱验证发信后端;生产应改为真实 SMTP / 邮件服务 | -| `DJANGO_DEFAULT_FROM_EMAIL` | 生产是 | `noreply@cmhub.example.com` | allauth 邮件默认发件人 | +| `DJANGO_EMAIL_BACKEND` | 否 | `django.core.mail.backends.console.EmailBackend` | 邮件发送后端;当前注册为免邮箱验证,不依赖邮件服务,后续密码找回/通知等邮件能力启用时生产应改为真实 SMTP / 邮件服务 | +| `DJANGO_DEFAULT_FROM_EMAIL` | 邮件能力启用时是 | `noreply@cmhub.example.com` | Django/allauth 邮件默认发件人 | | `DJANGO_SESSION_COOKIE_SECURE` | 生产是 | `true` | 生产 HTTPS 下 session cookie 仅允许安全连接传输 | | `DJANGO_CSRF_COOKIE_SECURE` | 生产是 | `true` | 生产 HTTPS 下 CSRF cookie 仅允许安全连接传输 | | `DJANGO_SECURE_SSL_REDIRECT` | 否 | `false` | 若 Nginx/宝塔已强制 HTTPS,可保持 `false`;若由 Django 强制跳转则设 `true` | diff --git a/docs/mvp-acceptance.md b/docs/mvp-acceptance.md index 264df73..f87d57d 100644 --- a/docs/mvp-acceptance.md +++ b/docs/mvp-acceptance.md @@ -8,7 +8,7 @@ | P0 验收项 | 结论 | 主要证据 | | --- | --- | --- | -| 用户注册 / 登录 | PASS | `apps.portal`:注册创建 0 点钱包且不写赠点流水、未验证邮箱不能登录、已验证用户可登录/登出 | +| 用户注册 / 登录 | PASS | `apps.portal`:注册创建 0 点钱包且不写赠点流水;T-605 后注册为免邮箱验证、注册后可直接登录,邮箱仍必填且唯一 | | API Key 自助管理 | PASS | `apps.portal`:Key 明文只显示一次、列表仅显示 prefix、删除为吊销、不可删除他人 Key;`apps.api`:缺失/无效 Key 为 401,吊销 Key 为 403 | | 扫码充值 | PASS | `apps.portal` / `apps.api`:创建 pending 订单不直接加点、不写流水;二维码票据展示;状态轮询只允许本人订单;主动查单可补入账 | | 个人中心 / 记录 | PASS | `apps.portal`:余额、充值总额、入账点数、消费/退款记录与实际订单/流水一致;充值记录和消费记录分页且仅见本人 | @@ -42,4 +42,3 @@ - 测试阶段仍有 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的 `models.W036` 警告;本项目账号邮箱唯一性由 `user.email` 唯一约束承担。 - 真实微信/支付宝商户密钥、证书、生产 SDK 依赖和公网回调地址仍待提供;MVP 验收使用 mock 支付路径。 - 真实 AI 上游调用和图片同步真实耗时仍需配置 `AI_KEY_ENCRYPTION_KEY`、导入真实 AiModel/ModelAlias 后执行;部署超时口径已登记到 T-403。 - diff --git a/docs/project-brief.md b/docs/project-brief.md index d0db6e7..1eb2eee 100644 --- a/docs/project-brief.md +++ b/docs/project-brief.md @@ -104,11 +104,12 @@ - Phase 1 已完成 T-101~T-104:AI Provider 适配器、AiModel/ModelAlias、Fernet 加密密钥存储、别名解析、配置审计与录制标题生成 smoke 已落地。 - Phase 2 计费核心已完成:PricingRule / ExchangeRate、计费计算、并发安全扣点与失败退点已落地,并覆盖并发测试。 - Phase 3 对外 API 与充值已完成到 T-306:API Key 鉴权、生成/余额接口、扫码充值下单与轮询、支付回调幂等入账、`image_url` SSRF 防护、生成/认证限流和充值金额上限已落地。 -- Phase 4 已完成 T-501~T-505:用户端注册 / 登录(allauth)、邮箱验证、登出、API Key 自助管理、个人中心汇总、充值记录、消费记录和充值页已落地;用户可创建充值订单、查看二维码票据并轮询到账;Key 明文只显示一次,库内只保留 hash 和 prefix,删除即吊销;记录页仅见本人数据并支持分页;Bootstrap 与 qrcode.js 已改为本地 static 自托管。 +- Phase 4 已完成 T-501~T-505:用户端注册 / 登录(allauth,免邮箱验证、注册即可用,邮箱仍唯一)、登出、API Key 自助管理、个人中心汇总、充值记录、消费记录和充值页已落地;用户可创建充值订单、查看二维码票据并轮询到账;Key 明文只显示一次,库内只保留 hash 和 prefix,删除即吊销;记录页仅见本人数据并支持分页;Bootstrap 与 qrcode.js 已改为本地 static 自托管。 - Phase 5 已完成 T-401:运营后台可管理/检索用户、钱包、API Key(脱敏)、计费规则、汇率、充值订单、点数流水和调用记录;手工调点必须填写原因,并经计费层锁钱包、写 `adjust` 流水。 - Phase 5 已完成 T-402:MVP P0 验收通过,注册/充值/API Key/调用/余额/记录/后台/别名映射均有测试证据,详见 `mvp-acceptance.md`。 -- Phase 5 已完成 T-403:已补部署 / 运行文档,明确宝塔/Nginx/Gunicorn、生产静态与媒体文件、真实邮件后端、共享缓存限流、图片同步超时、真实商户配置和上线检查,详见 `deployment.md`。 -- 下一步是按 `deployment.md` 在 VPS 上配置真实环境,并补跑真实支付和真实 AI 图片耗时验证。 +- Phase 5 已完成 T-403:已补部署 / 运行文档,明确宝塔/Nginx/Gunicorn、生产静态与媒体文件、共享缓存限流、图片同步超时、真实商户配置和上线检查;当前免邮箱验证,邮件服务仅作为后续密码找回/通知等邮件能力配置项,详见 `deployment.md`。 +- Phase 6 已完成 T-601 可用别名发现、T-602 后台表名/分组中文化与 T-605 免邮箱验证策略落地;T-603 字段级中文化代码和迁移已完成,待稳定 MySQL 测试库补跑完整验证后收尾。 +- 下一步是完成 T-603 验证收尾,并继续补跑真实支付回调到账闭环、真实 AI 模型导入和真实图片耗时验证。 --- *更多细节:愿景 `01-vision.md` | 需求与验收 `02-requirements.md` | 架构 `04-architecture.md` | 任务计划 `06-tasks.md`。* diff --git a/progress.md b/progress.md index f0c7e47..8dc51ff 100644 --- a/progress.md +++ b/progress.md @@ -1216,3 +1216,28 @@ - 诚实标注归一化误伤风险并要求正常文本测试;不暴露未实现的输出审核配置。 - 实现时须盯的一点(skeleton 与新规格冲突,非文档错):现有未提交的 `generation.py` skeleton 是「先 load_image_input 再 moderate_input(含 image)」+ 有 output 钩子;T-604 实现必须按新规格翻正——① prompt 审核挪到图片下载之前;② moderate_input 改纯 prompt;③ 去掉/休眠 output 钩子与未实现的退点配置。 - 小注意(codex 已自标、可接受):归一化误伤需正常文本测试兜底;每请求一次共享 cache 版本检查(DatabaseCache 为一次 DB 读)。 + +## 2026-07-06 T-605:落实免邮箱验证策略 + +- 状态:DONE。 +- 目标:把 `ACCOUNT_EMAIL_VERIFICATION="none"` 作为长期既定策略清理落地,注册即可用、不发验证邮件,邮箱仍必填且唯一。 +- 代码变更: + - `config/settings.py`:`ACCOUNT_EMAIL_VERIFICATION` 改为干净的 `"none"`;`ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION=False`,避免免验证策略下保留“点确认邮件自动登录”的误导配置。 + - `apps/portal/tests.py`:更新注册 / 登录测试口径。注册测试改为断言不发验证邮件、创建 0 点钱包、不写赠点流水且注册后已登录;未验证邮箱用户可直接登录;普通登录用例名称去掉 `verified` 语义。 +- 文档变更: + - `README.md`、`docs/00-ai-start-here.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md`、`docs/deployment.md`、`docs/env.md`、`docs/mvp-acceptance.md`、`docs/project-brief.md`:同步“免邮箱验证、注册即可用,邮箱仍唯一”口径。 + - 生产部署文档不再把真实邮件服务作为注册登录前置条件;邮件服务仅用于后续密码找回、通知或恢复邮箱验证等邮件能力。 + - T-603 从“邮箱验证口径阻塞”解封,状态改为 `TODO`:字段中文化代码/迁移已完成,待稳定 MySQL 测试库补跑完整验证后再标 `DONE`。 +- 验证: + - `py -3.12 -m py_compile config\settings.py apps\portal\tests.py`:通过。 + - `py -3.12 manage.py check`:通过,0 issues。 + - `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。 + - `py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests.test_signup_creates_user_wallet_with_zero_points_no_ledger_and_can_login apps.portal.tests.PortalAccountFlowTests.test_user_can_login_without_verified_email apps.portal.tests.PortalAccountFlowTests.test_user_can_login_view_dashboard_and_logout --keepdb --noinput --verbosity 2`:通过,3 tests OK。 + - `py -3.12 manage.py test apps.portal --keepdb --noinput --verbosity 2`:通过,26 tests OK。 + - `git diff --check`:通过,仅 Windows CRLF 提示。 +- 全量测试说明: + - `py -3.12 manage.py test --keepdb --noinput --verbosity 2`:已尝试,发现 131 tests,跑到 95 tests 后远程 MySQL `43.128.3.240` 连接超时 / WinError 10051,最终 22 errors。 + - `Test-NetConnection 43.128.3.240 -Port 3306`:随后显示 `TcpTestSucceeded=True`,说明端口间歇可达。 + - 单独复跑失败起点 `apps.billing.tests.BillingServiceTests apps.billing.tests.BillingAdminTests apps.billing.tests.ConcurrentDebitTests`:17 条业务用例 OK;并发扣点用例本体打印 `ok` 后在测试库 flush 阶段再次因 MySQL 连接超时 / WinError 10060 记 ERROR。 + - 结论:T-605 相关测试已通过,全量单次绿受远程 MySQL 长跑稳定性阻塞,不是本任务断言失败。 +- 下一步:回到 T-603,在更稳定的 MySQL 测试库或网络条件下补跑完整验证,再决定是否标 `DONE`。