diff --git a/README.md b/README.md index f90f30f..728fea2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## 它做什么 -- 用户端(自助):公开首页、注册/登录、扫码充值、查看充值记录/剩余点数/消费记录、查看可用模型、生成与删除 API Key、下载桌面端(新用户注册成功赠送 100 点试用点数)。 +- 用户端(自助):公开首页、注册/登录、扫码充值、查看充值记录/剩余点数/消费记录、查看可用模型、生成与删除 API Key、下载桌面端(新用户注册成功赠送 10 点试用点数)。 - 对外提供 HTTP API:生成标题、生成图片(旧同步接口 + 新异步提交/轮询接口)、多张图片理解并返回文字、查询点数余额、查询可用能力别名。 - 按「操作类型 + 能力别名(+ 可选分辨率)」计费,调用消耗不同点数;余额不足返回「点数不足,请先充值」。 - **预付费点数模型**:用户在外部支付系统充值,付款成功由支付系统回调本服务,按汇率把金额转成点数存在本地;之后调用直接扣本地点数,扣费与上游解耦、低延迟。 @@ -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/T-603 django-admin 中文化、T-604 中文敏感词本地过滤、T-605 免邮箱验证策略落地、T-606 公开首页 + 客户端下载入口、T-607 桌面端最新版本检查接口、T-608 新用户注册赠送 100 点试用点数、T-609 桌面端版本强制更新标记、T-610 首页导入模板下载入口、T-611 用户端品牌名统一为“虾皮圈”、T-612 生图同步接口止血、T-613 抽生成核心 service、T-614 生图异步任务化接口、T-615 旧同步生图接口遥测 / 弃用口径、T-616 生图失败自动重试 2 次、T-617 桌面端版本文件大小字段与 T-618 客户端发布版本后台必填校验元数据。用户可通过公开首页进入注册、登录、下载客户端和下载导入模板;新用户注册后经计费层自动获得 100 点并写注册赠点流水;登录后可扫码充值并轮询到账,生成 / 删除(吊销)API Key,查看余额、充值总额、分页充值记录、分页点数记录与可用模型;桌面端可匿名请求最新客户端版本 JSON,并读取 `release.force_update` 判断是否必须升级、读取 `release.size_bytes` 校验安装包大小;新版桌面端可用异步生图提交 / 轮询接口,旧同步生图接口继续兼容并写结构化用量日志;运营可在 django-admin 检索用户、钱包、API Key、计费规则、汇率、充值订单、点数流水、注册赠点记录、调用记录、图片生成任务、客户端发布版本和导入模板,客户端发布版本新增 / 编辑时必须填写 SHA256 与文件大小,并通过计费层带原因手工调点。生产侧仍需补真实支付回调到账闭环,并按日志观察旧同步生图接口迁移进度。详见 [`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/T-603 django-admin 中文化、T-604 中文敏感词本地过滤、T-605 免邮箱验证策略落地、T-606 公开首页 + 客户端下载入口、T-607 桌面端最新版本检查接口、T-608 新用户注册赠送试用点数(当前 10 点)、T-609 桌面端版本强制更新标记、T-610 首页导入模板下载入口、T-611 用户端品牌名统一为“虾皮圈”、T-612 生图同步接口止血、T-613 抽生成核心 service、T-614 生图异步任务化接口、T-615 旧同步生图接口遥测 / 弃用口径、T-616 生图失败自动重试 2 次、T-617 桌面端版本文件大小字段与 T-618 客户端发布版本后台必填校验元数据。用户可通过公开首页进入注册、登录、下载客户端和下载导入模板;新用户注册后经计费层自动获得 10 点并写注册赠点流水;登录后可扫码充值并轮询到账,生成 / 删除(吊销)API Key,查看余额、充值总额、分页充值记录、分页点数记录与可用模型;桌面端可匿名请求最新客户端版本 JSON,并读取 `release.force_update` 判断是否必须升级、读取 `release.size_bytes` 校验安装包大小;新版桌面端可用异步生图提交 / 轮询接口,旧同步生图接口继续兼容并写结构化用量日志;运营可在 django-admin 检索用户、钱包、API Key、计费规则、汇率、充值订单、点数流水、注册赠点记录、调用记录、图片生成任务、客户端发布版本和导入模板,客户端发布版本新增 / 编辑时必须填写 SHA256 与文件大小,并通过计费层带原因手工调点。生产侧仍需补真实支付回调到账闭环,并按日志观察旧同步生图接口迁移进度。详见 [`docs/current-state.md`](docs/current-state.md)。 T-619 已完成:新增独立 `vision` 能力和同步 `POST /api/v1/analyze/images` 多图理解接口,按一次请求固定扣点;既有标题、生图和异步生图接口保持兼容。生产启用前需在 admin 配置同时具备 `text`、`vision` 能力的模型、`vision-standard` 默认别名及默认计费规则。T-620 已完成:同步 / 异步图生图兼容旧单图字段并支持有序多图,第一张为主商品图、后续图为参考图。 diff --git a/apps/billing/migrations/0009_signup_bonus_default_ten.py b/apps/billing/migrations/0009_signup_bonus_default_ten.py new file mode 100644 index 0000000..afd7e7b --- /dev/null +++ b/apps/billing/migrations/0009_signup_bonus_default_ten.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.15 on 2026-07-18 06:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('billing', '0008_alter_callrecord_operation_type_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='signupbonusgrant', + name='points_granted', + field=models.BigIntegerField(default=10, verbose_name='赠送点数'), + ), + ] diff --git a/apps/billing/models.py b/apps/billing/models.py index 8fe84cf..ea1a326 100644 --- a/apps/billing/models.py +++ b/apps/billing/models.py @@ -318,7 +318,7 @@ class SignupBonusGrant(models.Model): on_delete=models.PROTECT, related_name="signup_bonus_grant", ) - points_granted = models.BigIntegerField("赠送点数", default=100) + points_granted = models.BigIntegerField("赠送点数", default=10) created_at = models.DateTimeField("创建时间", auto_now_add=True) class Meta: diff --git a/apps/billing/services.py b/apps/billing/services.py index d18af24..954272e 100644 --- a/apps/billing/services.py +++ b/apps/billing/services.py @@ -352,7 +352,7 @@ def adjust_wallet_points( def grant_signup_bonus( *, user, - points: int = 100, + points: int = 10, reason: str = "new_user_registration", ) -> SignupBonusGrantResult: points = _validate_positive_points(points) diff --git a/apps/billing/tests.py b/apps/billing/tests.py index 452a175..4c226bd 100644 --- a/apps/billing/tests.py +++ b/apps/billing/tests.py @@ -501,18 +501,18 @@ class BillingServiceTests(TestCase): wallet = UserWallet.objects.get(user=user) self.assertTrue(first.granted) self.assertFalse(second.granted) - self.assertEqual(first.points_granted, 100) + self.assertEqual(first.points_granted, 10) self.assertEqual(second.points_granted, 0) - self.assertEqual(first.balance_after, 100) - self.assertEqual(second.balance_after, 100) - self.assertEqual(wallet.points_balance, 100) + self.assertEqual(first.balance_after, 10) + self.assertEqual(second.balance_after, 10) + self.assertEqual(wallet.points_balance, 10) self.assertEqual(SignupBonusGrant.objects.filter(user=user).count(), 1) ledger = PointsLedger.objects.get( user=user, change_type=PointsLedger.ChangeType.SIGNUP_BONUS, ) - self.assertEqual(ledger.points_delta, 100) - self.assertEqual(ledger.balance_after, 100) + self.assertEqual(ledger.points_delta, 10) + self.assertEqual(ledger.balance_after, 10) self.assertEqual(ledger.reason, "new_user_registration") def test_grant_signup_bonus_adds_to_existing_wallet_balance(self): @@ -528,8 +528,8 @@ class BillingServiceTests(TestCase): wallet = UserWallet.objects.get(user=user) self.assertTrue(result.granted) - self.assertEqual(wallet.points_balance, 130) - self.assertEqual(result.balance_after, 130) + self.assertEqual(wallet.points_balance, 40) + self.assertEqual(result.balance_after, 40) def test_create_recharge_order_marks_order_failed_when_gateway_fails(self): self.create_exchange_rate(points_per_unit="10.0000") @@ -1061,7 +1061,7 @@ class ConcurrentSignupBonusTests(TransactionTestCase): self.assertEqual(sum(1 for granted, _balance in results if not granted), 1) wallet = UserWallet.objects.get(user=self.user) - self.assertEqual(wallet.points_balance, 100) + self.assertEqual(wallet.points_balance, 10) self.assertEqual(SignupBonusGrant.objects.filter(user=self.user).count(), 1) self.assertEqual( PointsLedger.objects.filter( diff --git a/apps/portal/templates/account/signup.html b/apps/portal/templates/account/signup.html index 14b1c39..7bd50ff 100644 --- a/apps/portal/templates/account/signup.html +++ b/apps/portal/templates/account/signup.html @@ -6,7 +6,7 @@

注册

- 注册成功后自动赠送 100 点试用点数,系统会写入注册赠点流水。 + 注册成功后自动赠送 10 点试用点数,系统会写入注册赠点流水。
{% csrf_token %} diff --git a/apps/portal/templates/portal/home.html b/apps/portal/templates/portal/home.html index 8f08c35..667016f 100644 --- a/apps/portal/templates/portal/home.html +++ b/apps/portal/templates/portal/home.html @@ -18,7 +18,7 @@ {% if user.is_authenticated %} 进入控制台 {% else %} - 注册领 100 点 + 注册领 10 点 登录 {% endif %} {% if current_release and current_release.has_download %} @@ -31,7 +31,7 @@ {% endif %}
- 注册送 100 点 + 注册送 10 点 按点数扣点 API Key 接入
@@ -73,7 +73,7 @@
01

注册账号

-

邮箱注册后直接登录,系统通过点数账本发放 100 点试用额度并写注册赠点流水。

+

邮箱注册后直接登录,系统通过点数账本发放 10 点试用额度并写注册赠点流水。

02
diff --git a/apps/portal/tests.py b/apps/portal/tests.py index 34ad350..c8f5928 100644 --- a/apps/portal/tests.py +++ b/apps/portal/tests.py @@ -176,7 +176,7 @@ class PortalAccountFlowTests(TestCase): def test_signup_rate_limit_is_configured(self): self.assertEqual(settings.ACCOUNT_RATE_LIMITS["signup"], "20/m/ip") - def test_signup_grants_100_points_writes_signup_bonus_ledger_and_can_login(self): + def test_signup_grants_10_points_writes_signup_bonus_ledger_and_can_login(self): suffix = uuid.uuid4().hex[:8] email = f"signup-{suffix}@example.com" @@ -196,22 +196,22 @@ class PortalAccountFlowTests(TestCase): wallet = UserWallet.objects.get(user=user) self.assertFalse(email_address.verified) self.assertTrue(email_address.primary) - self.assertEqual(wallet.points_balance, 100) + self.assertEqual(wallet.points_balance, 10) self.assertEqual(SignupBonusGrant.objects.filter(user=user).count(), 1) ledger = PointsLedger.objects.get( user=user, change_type=PointsLedger.ChangeType.SIGNUP_BONUS, ) - self.assertEqual(ledger.points_delta, 100) - self.assertEqual(ledger.balance_after, 100) + self.assertEqual(ledger.points_delta, 10) + self.assertEqual(ledger.balance_after, 10) self.assertEqual(len(mail.outbox), 0) self.assertTrue(get_user(self.client).is_authenticated) dashboard_response = self.client.get("/dashboard") self.assertEqual(dashboard_response.status_code, 200) - self.assertEqual(dashboard_response.context["balance"].points_balance, 100) - self.assertEqual(dashboard_response.context["balance"].ledger_balance, 100) - self.assertContains(dashboard_response, "100") + self.assertEqual(dashboard_response.context["balance"].points_balance, 10) + self.assertEqual(dashboard_response.context["balance"].ledger_balance, 10) + self.assertContains(dashboard_response, "10") def test_user_can_login_without_verified_email(self): suffix = uuid.uuid4().hex[:8] @@ -282,8 +282,8 @@ class PortalAccountFlowTests(TestCase): self.assertEqual(response.resolver_match.url_name, "portal-home") self.assertContains(response, "虾皮圈 AI 电商生成台") self.assert_portal_branding(response) - self.assertContains(response, "注册领 100 点") - self.assertContains(response, "注册送 100 点") + self.assertContains(response, "注册领 10 点") + self.assertContains(response, "注册送 10 点") self.assertContains(response, "登录") self.assertContains(response, "客户端暂未发布") self.assertContains(response, "暂未发布") @@ -404,7 +404,7 @@ class PortalAccountFlowTests(TestCase): self.assertEqual(response.status_code, 200) self.assertContains(response, "进入控制台") - self.assertNotContains(response, "注册领 100 点") + self.assertNotContains(response, "注册领 10 点") def test_download_release_only_keeps_one_current_per_platform(self): old_release = self.create_download_release(version="1.0.0", sha256="c" * 64) diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md index c438c7e..9cf9fd9 100644 --- a/docs/00-ai-start-here.md +++ b/docs/00-ai-start-here.md @@ -6,7 +6,7 @@ `cmhub` 是「**自助用户端 + 计费 API + 运营后台**」三合一服务:终端用户在用户端自助注册、扫码充值、管理 API Key,用 API Key 调用「生成标题」「生成图片」「多图理解」等 HTTP 接口,按**点数计费**;运营用 django-admin 管理用户、点数与记录。 -第一版 MVP 做:**用户自助注册登录 → 注册成功赠送 100 点试用点数 → 扫码充值转点数 → 自助生成/删除 API Key → 带 Key 调用按规则算点数 → 点数足够则调上游生成并扣点 / 不足则报错 → 写调用与消费记录 → django-admin 运营后台**。注册赠点也是账本资产,必须经计费层入账并写点数流水。 +第一版 MVP 做:**用户自助注册登录 → 注册成功赠送 10 点试用点数 → 扫码充值转点数 → 自助生成/删除 API Key → 带 Key 调用按规则算点数 → 点数足够则调上游生成并扣点 / 不足则报错 → 写调用与消费记录 → django-admin 运营后台**。注册赠点也是账本资产,必须经计费层入账并写点数流水。 ## 必读顺序 @@ -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 字段级中文化」、T-604「中文敏感词本地过滤」、T-605「免邮箱验证策略落地」、T-606「公开首页 + 客户端下载入口」、T-607「桌面端最新版本检查接口」、T-608「新用户注册赠送 100 点试用点数」、T-609「桌面端版本检查接口增加强制更新标记」、T-610「首页导入模板下载入口」、T-611「用户端品牌名统一为虾皮圈」、T-612「生图同步接口止血」、T-613「抽生成核心 service」、T-614「生图异步任务化接口」与 T-615「旧同步生图接口遥测 / 弃用口径」。生图慢 / 504 / 客户端超时已拆为 T-612~T-615 并完成:T-612 已做同步接口上游硬截止止血,T-613 已抽共享生成 core,T-614 已新增异步提交 / 轮询接口与 DB worker,T-615 已给旧同步 / 新异步提交路径接入结构化日志并形成旧同步接口退出条件。生产侧仍需补真实支付回调到账闭环;邮件服务仅用于后续密码找回/通知等邮件能力,不阻塞注册登录。 +当前项目处于:**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 字段级中文化」、T-604「中文敏感词本地过滤」、T-605「免邮箱验证策略落地」、T-606「公开首页 + 客户端下载入口」、T-607「桌面端最新版本检查接口」、T-608「新用户注册赠送试用点数(当前 10 点)」、T-609「桌面端版本检查接口增加强制更新标记」、T-610「首页导入模板下载入口」、T-611「用户端品牌名统一为虾皮圈」、T-612「生图同步接口止血」、T-613「抽生成核心 service」、T-614「生图异步任务化接口」与 T-615「旧同步生图接口遥测 / 弃用口径」。生图慢 / 504 / 客户端超时已拆为 T-612~T-615 并完成:T-612 已做同步接口上游硬截止止血,T-613 已抽共享生成 core,T-614 已新增异步提交 / 轮询接口与 DB worker,T-615 已给旧同步 / 新异步提交路径接入结构化日志并形成旧同步接口退出条件。生产侧仍需补真实支付回调到账闭环;邮件服务仅用于后续密码找回/通知等邮件能力,不阻塞注册登录。 T-616~T-620 也已完成:生图失败自动重试、客户端版本文件校验元数据、多图理解 `vision` 操作与同步接口,以及单图 / 多图图生图均已落地。T-620 保持旧单图字段兼容,并固定第一张为主图、后续为参考图;生产启用多图理解前仍需在 admin 配置视觉模型、默认别名和计费规则。 @@ -70,7 +70,7 @@ T-616~T-620 也已完成:生图失败自动重试、客户端版本文件校 MVP 只做: -- **用户端(Django 模板 SSR + Bootstrap)**:自助注册/登录、注册成功一次性赠送 100 点试用点数、扫码充值、查看充值记录/充值总额/剩余点数/消费记录、自助生成与删除 API Key。 +- **用户端(Django 模板 SSR + Bootstrap)**:自助注册/登录、注册成功一次性赠送 10 点试用点数、扫码充值、查看充值记录/充值总额/剩余点数/消费记录、自助生成与删除 API Key。 - 对外生成 / 理解接口:`POST /api/v1/generate/title`(同步返回标题)、`POST /api/v1/generate/image`(旧同步生图,保留兼容)、`POST /api/v1/generate/image/tasks` + `GET /api/v1/generate/image/tasks/{task_id}`(异步生图提交 / 轮询)、`POST /api/v1/analyze/images`(同步多图理解并返回文字)。 - API Key 鉴权,识别所属注册用户(API 只认 Key,不认 Web session)。 - 点数计费:按「操作类型 + 能力别名(+ 可选分辨率)」查计费规则得点数;本地余额原子扣减;不足报错。 @@ -83,7 +83,7 @@ MVP 不做: - Celery/RQ 等外部任务队列(T-614 已用 DB 任务表 + management command worker 实现生图异步提交 / 轮询;本期不做 cancel、回调通知或专业队列)。 - 自建支付/收银台(充值走外部支付系统,本服务发起扫码下单并接收回调,不碰资金清算)。 - 面向终端消费者的「内容生成产品页」(用户端只做账户自助,生成结果仍只经 API 输出)。 -- 复杂活动赠点、邀请奖励或历史用户自动补发;除 T-608 明确定义的新用户一次性 100 点注册试用额度外,不做其他免费点数活动。 +- 复杂活动赠点、邀请奖励或历史用户自动补发;除 T-608 明确定义的新用户一次性 10 点注册试用额度外,不做其他免费点数活动。 - 多币种、分账、自动退款对账。 ## 事实来源 diff --git a/docs/01-vision.md b/docs/01-vision.md index db18618..abc24f4 100644 --- a/docs/01-vision.md +++ b/docs/01-vision.md @@ -21,7 +21,7 @@ - **核心流程优先**:先把「鉴权 → 计费 → 生成 → 扣点 → 记录」这条主链路做顺。 - **真实数据优先**:点数、金额、扣费、充值不造假;不用演示逻辑冒充计费逻辑。 - **账目必须对得上**:每一笔点数变动都有流水、可追溯到充值订单或调用记录。 -- **试用额度可控**:新用户注册送 100 点试用点数降低首次接入门槛,但必须按账本发放、可追溯、可防重复。 +- **试用额度可控**:新用户注册送 10 点试用点数降低首次接入门槛,但必须按账本发放、可追溯、可防重复。 - **小步交付**:每一步都能运行、能验证、能回退。 - **少即是稳**:MVP 不追求完整,只追求最小闭环可靠;先同步、先单机、先后台手工开通。 - **可维护**:复用 `cmbot` 现有 AI 调用代码,不另起一套。 @@ -40,7 +40,7 @@ - 不做自建支付/收银台,付款在外部支付系统完成,本服务发起扫码下单并接收回调,不碰资金清算。 - 不做异步任务队列(第一版图片生成走同步等待)。 - 不做面向终端消费者的「内容生成产品页」;用户端只做账户自助(注册/充值/API Key 管理/记录查询)。 -- 不做复杂活动赠点、邀请奖励或历史用户自动补发;当前只做新用户注册一次性 100 点试用额度,且必须配套防刷、幂等和流水留痕。 +- 不做复杂活动赠点、邀请奖励或历史用户自动补发;当前只做新用户注册一次性 10 点试用额度,且必须配套防刷、幂等和流水留痕。 - 不做多币种、分账、自动退款对账等容易膨胀、不属于 MVP 的能力。 > MVP 的具体功能范围与验收标准,见 [需求](02-requirements.md)。 diff --git a/docs/02-requirements.md b/docs/02-requirements.md index de5e5ed..74974dc 100644 --- a/docs/02-requirements.md +++ b/docs/02-requirements.md @@ -25,7 +25,7 @@ | 功能 | 用户能做什么 | 优先级 | | --- | --- | --- | -| 用户注册/登录 | 终端用户在用户端自助注册、登录(**免邮箱验证、注册即可用;邮箱仍必填且唯一**);注册成功一次性赠送 **100 点**试用点数 | P0 | +| 用户注册/登录 | 终端用户在用户端自助注册、登录(**免邮箱验证、注册即可用;邮箱仍必填且唯一**);注册成功一次性赠送 **10 点**试用点数 | P0 | | API Key 自助管理 | 登录后自助生成/删除 API Key;Key 只在生成时显示一次(库内哈希存储) | P0 | | 扫码充值 | 用户端发起充值→展示支付二维码→扫码付款→回调到账;金额按汇率转点数 | P0 | | 个人中心 | 查看剩余点数、充值总额、充值记录、点数使用(消费)记录 | P0 | @@ -48,13 +48,13 @@ | --- | --- | --- | | 旧同步生图接口用量遥测与弃用 | 观察旧同步生图调用量、错误率和客户端版本,满足条件后再单独任务下线旧同步接口 | V2 | | 用量统计报表 | 按用户/时间/模型聚合消耗与成本 | V2 | -| 复杂活动赠点 / 邀请奖励 | 注册 100 点之外的活动额度、邀请返利、历史用户补发等,需单独风控与审批 | V2 | +| 复杂活动赠点 / 邀请奖励 | 注册 10 点之外的活动额度、邀请返利、历史用户补发等,需单独风控与审批 | V2 | | API Key 轮换 / 授权 | Key 到期轮换、按 Key 限授权别名 | V2 | | 退款对账自动化 | 外部退款联动本地点数冲正 | V3 | ## 四、核心用户故事(MVP) -1. 作为新用户,我在用户端自助注册登录后先获得 100 点试用点数;后续扫码充值一笔钱,看到对应点数按汇率到账。 +1. 作为新用户,我在用户端自助注册登录后先获得 10 点试用点数;后续扫码充值一笔钱,看到对应点数按汇率到账。 2. 作为已登录用户,我自助生成一把 API Key(明文只显示一次),也能删除不用的 Key。 3. 作为接入方,我带着 API Key 调用生成标题接口,传入 prompt,能拿到生成的标题。 4. 作为接入方,我可以提交一张或多张图片生成商品图:第一张作为主商品图,后续图片仅作风格、构图、场景或排版参考;可调用旧同步接口在同一个响应里拿到图片结果,也可先提交异步图片任务拿到 `task_id` 再轮询直到成功或失败。 @@ -74,7 +74,7 @@ - **余额查询 API**:返回的余额等于该账号点数流水累加结果。 - **充值转点数**:模拟一次支付系统回调(含订单号、金额、签名),验签通过后按汇率加点;**同一订单号重复回调只入账一次**(幂等);验签失败不入账。 - **调用记录 / 点数流水**:每次成功/失败调用、每次充值/退款/调整/注册赠点都各生成一条记录,字段完整、可在后台检索。 -- **用户注册/登录**:新用户能自助注册(**免邮箱验证、注册即可用;邮箱仍必填且唯一**)、登录;注册成功后点数为 100,且有一条对应的注册赠点流水。重复提交或服务重试不得重复赠送。 +- **用户注册/登录**:新用户能自助注册(**免邮箱验证、注册即可用;邮箱仍必填且唯一**)、登录;注册成功后点数为 10,且有一条对应的注册赠点流水。重复提交或服务重试不得重复赠送。 - **API Key 自助管理**:登录用户能生成 API Key(明文只显示一次,库内存哈希)、能删除;删除落库为吊销,吊销后该 Key 调用返回 403,缺失/无效/不存在 Key 返回 401。 - **扫码充值**:用户端发起充值→展示二维码→(mock 或真实)支付成功回调后点数按汇率到账,充值记录与流水各一条;同一订单号重复回调只入账一次。 - **个人中心**:剩余点数 = 点数流水累加;充值总额、充值记录、消费记录与实际一致。 @@ -92,7 +92,7 @@ | 生成返回方式 | 标题仍同步返回;图片旧同步接口保留兼容,新版客户端优先使用异步提交 / 轮询 | | 模型选择方式 | 对外用**能力别名**(如 `image-hd`),不暴露具体模型名;后台配置别名→模型映射 | | 充值入口 | 用户端自助发起扫码充值(本服务向支付系统下单取二维码),付款成功由支付系统**服务端回调**入账 | -| 注册是否送点数 | 是。新用户注册成功一次性赠送 100 点试用点数;只对功能上线后的新注册用户自动发放,历史用户是否补发需另行审批和单独任务 | +| 注册是否送点数 | 是。新用户注册成功一次性赠送 10 点试用点数;只对本次额度调整上线后的新注册用户自动发放,历史用户是否补发需另行审批和单独任务 | | 暂不支持 | 复杂活动赠点 / 邀请奖励、多币种、分账、自动退款对账;异步生图本期只做提交 / 轮询,不做 cancel、回调通知或 Celery/RQ | ## 七、待确认 / 风险点 diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index abbecc5..3786b87 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-605 起注册策略固定为**免邮箱验证、注册即可用,邮箱仍必填且唯一**;T-608 已落地新用户注册成功经计费层赠送 100 点并写注册赠点流水;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-608 已落地新用户注册成功经计费层赠送 10 点并写注册赠点流水;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` | @@ -41,7 +41,7 @@ - **新异步接口优先给新版客户端**:新版桌面端提交 `/api/v1/generate/image/tasks` 获得 `task_id`,再轮询 `/api/v1/generate/image/tasks/{task_id}`,避免 HTTP 长连接占住生成池,也支持客户端重启后继续查结果。 - **任务队列选择**:T-614 采用数据库任务表 + management command worker,不引入 Celery/RQ/Redis;后续只有在 DB 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;T-605 起注册策略固定为 `ACCOUNT_EMAIL_VERIFICATION="none"`,免邮箱验证、注册即可用,邮箱仍必填且唯一;T-608 已把注册成功后的初始化改为经 `apps.billing` 发放 100 点试用点数并写 `signup_bonus` 流水,且显式配置 `ACCOUNT_SIGNUP_RATE_LIMIT` 做基础注册限流。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,哈希存储)。新注册用户一次性赠送 100 点,必须走计费层和点数流水。 +- **用户端用 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"`,免邮箱验证、注册即可用,邮箱仍必填且唯一;T-608 已把注册成功后的初始化改为经 `apps.billing` 发放 10 点试用点数并写 `signup_bonus` 流水,且显式配置 `ACCOUNT_SIGNUP_RATE_LIMIT` 做基础注册限流。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,哈希存储)。新注册用户一次性赠送 10 点,必须走计费层和点数流水。 ## 三、构建与运行命令 diff --git a/docs/04-architecture.md b/docs/04-architecture.md index 3a35bc4..1d988b5 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -18,7 +18,7 @@ 组件落位: -- **用户端层(Django 模板 SSR)**:公开首页、注册/登录(Django auth / allauth)、个人中心(余额/充值总额/充值记录/点数记录)、API Key 自助管理、发起扫码充值、模型目录与客户端下载入口。入口 `apps/portal/`,公开首页匿名可访问,其他自助页面用 session 鉴权。T-501/T-608 已落地 `/signup`、`/login`、`/logout` 与 `/dashboard`;注册成功后经计费层一次性发放 100 点试用点数,并写 `signup_bonus` 点数流水。T-502 已落地 `/apikeys`,用户可自助生成和删除(吊销)自己的 API Key,明文只显示一次,列表只显示 prefix。T-503/T-608 已扩展 `/dashboard` 并新增 `/records/recharge`、`/records/usage`,只读展示当前用户余额、充值订单、注册赠点与消费/退款流水。T-504 已落地 `/recharge`,用户可创建 pending 充值订单、查看二维码票据,并轮询订单状态;到账仍以服务端回调或主动查单入账后的本地订单状态为准。T-505 已把 Bootstrap/qrcode.js 改成本地 static 自托管,并把充值/点数记录页从固定切片改为分页。T-606 已把 `/` 改为公开首页,并新增 `DownloadRelease` 下载版本配置用于展示 Windows 客户端版本、下载地址、SHA256 与发布说明;T-607/T-609/T-617 已新增公开 JSON 版本检查接口给桌面端自动更新使用,并返回强制更新标记和安装包文件大小字节数。T-610 已在公开首页下载区增加导入模板下载入口,由后台 `ImportTemplate` 配置当前模板。 +- **用户端层(Django 模板 SSR)**:公开首页、注册/登录(Django auth / allauth)、个人中心(余额/充值总额/充值记录/点数记录)、API Key 自助管理、发起扫码充值、模型目录与客户端下载入口。入口 `apps/portal/`,公开首页匿名可访问,其他自助页面用 session 鉴权。T-501/T-608 已落地 `/signup`、`/login`、`/logout` 与 `/dashboard`;注册成功后经计费层一次性发放 10 点试用点数,并写 `signup_bonus` 点数流水。T-502 已落地 `/apikeys`,用户可自助生成和删除(吊销)自己的 API Key,明文只显示一次,列表只显示 prefix。T-503/T-608 已扩展 `/dashboard` 并新增 `/records/recharge`、`/records/usage`,只读展示当前用户余额、充值订单、注册赠点与消费/退款流水。T-504 已落地 `/recharge`,用户可创建 pending 充值订单、查看二维码票据,并轮询订单状态;到账仍以服务端回调或主动查单入账后的本地订单状态为准。T-505 已把 Bootstrap/qrcode.js 改成本地 static 自托管,并把充值/点数记录页从固定切片改为分页。T-606 已把 `/` 改为公开首页,并新增 `DownloadRelease` 下载版本配置用于展示 Windows 客户端版本、下载地址、SHA256 与发布说明;T-607/T-609/T-617 已新增公开 JSON 版本检查接口给桌面端自动更新使用,并返回强制更新标记和安装包文件大小字节数。T-610 已在公开首页下载区增加导入模板下载入口,由后台 `ImportTemplate` 配置当前模板。 - **用户与账号层**:注册用户 `User`、点数钱包 `UserWallet`、`ApiKey`(一用户多把、哈希存储)。入口 `apps/users/`。 - **API 层(DRF)**:对外生成接口、异步图片任务接口、余额查询、支付回调接收、扫码下单、公开版本检查接口。入口 `apps/api/`。生成/余额/模型目录这类对外业务 API **只认 API Key,不接受 Web session**;充值下单/状态查询属于用户端流程,走 Web session + CSRF;支付回调走平台验签;T-607/T-609/T-617 的客户端下载版本检查接口为公开只读例外,不需要 API Key,不读取用户、不扣点,`release.force_update` 仅表示当前发布版本是否强制升级,`release.size_bytes` 仅表示下载文件大小字节数。 - **计费层**:点数计算、原子扣减(锁 `UserWallet` 行)、退点、充值入账、流水记账。入口 `apps/billing/`。 @@ -66,7 +66,7 @@ T-619 多图理解继续复用上述 URL 下载器和逐跳 SSRF 校验,并额 - 计费规则查询:按「操作类型 + 能力别名(+ 可选分辨率)」算出本次点数 N。**按别名定价,不按具体供应商 SKU 定价**,这样后台换底层模型时计费不变。 - 点数原子扣减与退回:数据库事务 + 行锁,保证并发不超扣、不为负。 - 充值入账:接收已验签的支付回调数据,按汇率换算点数,幂等入账,写流水。 -- 注册赠点:新用户注册成功后一次性发放 100 点试用点数,必须幂等、防并发重复,并写注册赠点流水。 +- 注册赠点:新用户注册成功后一次性发放 10 点试用点数,必须幂等、防并发重复,并写注册赠点流水。 - 手工调点:运营后台只收集点数变动和原因,必须调用计费层 `adjust_wallet_points()`;服务在事务内锁 `UserWallet`,拒绝扣成负数,并写 `PointsLedger(change_type=adjust)`。 - 点数流水记账:所有点数变动(充值/消费/调整/冲正/注册赠点)都生成一条流水。 - 是点数余额的唯一写入方。 @@ -259,7 +259,7 @@ CREATE TABLE user_wallet ( CREATE TABLE signup_bonus_grant ( id INTEGER PRIMARY KEY, user_id INTEGER UNIQUE NOT NULL REFERENCES "user"(id), - points_granted BIGINT NOT NULL DEFAULT 100, + points_granted BIGINT NOT NULL DEFAULT 10, created_at TEXT NOT NULL ); @@ -379,7 +379,7 @@ CREATE TABLE image_generation_task_input ( - `payment_user_id`、`payment_txn_no` 为对账预留,字段先建。 - `recharge_order.exchange_rate` 与 `points_granted` 在下单时写入,状态为 `pending` 时也必须有值;支付回调金额必须与订单金额一致,入账时不得按新的汇率重算。 - `call_record.status` 状态机为 `pending -> success / failed`。异步任务临时性失败等待重试时仍保持 `pending`;最终上游失败退点后才变为 `failed`,退款流水通过 `points_ledger(change_type=refund, ref_call_id=call_record.id)` 关联,不单独增加 `refunded` 状态,避免调用结果与账务动作混在一个字段里。 -- T-201 已落地 `UserWallet` / `ApiKey` 于 `apps.users`,`PointsLedger` / `CallRecord` 于 `apps.billing`;T-203 已落地扣点/退点服务;T-304 已落地 `RechargeOrder`、回调幂等入账服务和 `points_ledger(ref_order_id, change_type)` 复合唯一约束,`ref_order_id` 当前仍为数值引用 `RechargeOrder.id`;T-305 已落地 `create_recharge_order()`,负责创建 pending 订单、锁定汇率/点数并回填二维码票据;T-401 已落地 `adjust_wallet_points()`,手工调整点数必须带原因并写 `adjust` 流水,后台钱包余额字段只读;T-608 已新增 `signup_bonus` 流水类型和 MySQL 兼容的注册赠点幂等标记 `SignupBonusGrant(user UNIQUE)`,并把 allauth 自助注册路径改为调用 `grant_signup_bonus()` 发放 100 点;T-614 已在 `apps.api` 落地 `ImageGenerationTask` 与 `api.0001_initial` 迁移,使用 MySQL 兼容的 `(api_key, idempotency_key_hash)` 唯一约束处理幂等键,不使用条件唯一约束;T-616 已在 `ImageGenerationTask` 增加 `next_attempt_at` 与 `(status, next_attempt_at)` 索引,用于 worker 避免立即反复抢占等待重试的任务;T-502 已把 API Key 自助管理接到 `ApiKey.create_for_user()`,删除动作写为 `revoked` 状态而非物理删除;T-503/T-608 已把个人中心和记录页接到只读查询,余额用 `get_balance_snapshot()`,充值总额按 paid `RechargeOrder` 汇总,充值 / 注册赠点 / 消费 / 退款按 `PointsLedger` 汇总;T-504 已把 `/recharge` 页面接到 `create_recharge_order()` 与 `/api/v1/recharge/status`。 +- T-201 已落地 `UserWallet` / `ApiKey` 于 `apps.users`,`PointsLedger` / `CallRecord` 于 `apps.billing`;T-203 已落地扣点/退点服务;T-304 已落地 `RechargeOrder`、回调幂等入账服务和 `points_ledger(ref_order_id, change_type)` 复合唯一约束,`ref_order_id` 当前仍为数值引用 `RechargeOrder.id`;T-305 已落地 `create_recharge_order()`,负责创建 pending 订单、锁定汇率/点数并回填二维码票据;T-401 已落地 `adjust_wallet_points()`,手工调整点数必须带原因并写 `adjust` 流水,后台钱包余额字段只读;T-608 已新增 `signup_bonus` 流水类型和 MySQL 兼容的注册赠点幂等标记 `SignupBonusGrant(user UNIQUE)`,并把 allauth 自助注册路径改为调用 `grant_signup_bonus()` 发放 10 点;T-614 已在 `apps.api` 落地 `ImageGenerationTask` 与 `api.0001_initial` 迁移,使用 MySQL 兼容的 `(api_key, idempotency_key_hash)` 唯一约束处理幂等键,不使用条件唯一约束;T-616 已在 `ImageGenerationTask` 增加 `next_attempt_at` 与 `(status, next_attempt_at)` 索引,用于 worker 避免立即反复抢占等待重试的任务;T-502 已把 API Key 自助管理接到 `ApiKey.create_for_user()`,删除动作写为 `revoked` 状态而非物理删除;T-503/T-608 已把个人中心和记录页接到只读查询,余额用 `get_balance_snapshot()`,充值总额按 paid `RechargeOrder` 汇总,充值 / 注册赠点 / 消费 / 退款按 `PointsLedger` 汇总;T-504 已把 `/recharge` 页面接到 `create_recharge_order()` 与 `/api/v1/recharge/status`。 ## 四、计费时序(核心,务必照此实现) @@ -464,22 +464,22 @@ CREATE TABLE image_generation_task_input ( - **兜底**:回调可能丢失,须提供按 `order_no` 主动查单补入账;前端每 ~1s 轮询订单状态仅作 UX。 - 仅商户密钥/证书为真实值待提供(微信 appid/mchid/apiv3_key/证书、支付宝 appid/公私钥/RSA2、notify_url 域名),协议已明确,可先 mock。 -### 4.3 注册赠点(新用户 100 点试用额度) +### 4.3 注册赠点(新用户 10 点试用额度) ```text 1. allauth 注册成功并保存 User(邮箱必填且唯一,当前免邮箱验证) -2. 调用 billing.grant_signup_bonus(user, points=100) +2. 调用 billing.grant_signup_bonus(user, points=10) 3. 事务:创建或锁定该用户的 UserWallet;创建 SignupBonusGrant(user UNIQUE) 幂等标记 若标记已存在 → 直接返回已发放,不改余额、不写新流水 - 否则 points_balance += 100 - 写 points_ledger(signup_bonus, +100, balance_after, reason="new_user_registration") -4. 注册后 dashboard / balance API 读取到包含 100 点赠点后的余额 + 否则 points_balance += 10 + 写 points_ledger(signup_bonus, +10, balance_after, reason="new_user_registration") +4. 注册后 dashboard / balance API 读取到包含 10 点赠点后的余额 ``` 要点: - 注册赠点不是充值收入,但属于可消费点数,必须纳入点数账本和后台审计。 -- 不允许在 adapter / view / signal 中直接 `points_balance=100`;所有余额变动必须走计费层。 +- 不允许在 adapter / view / signal 中直接 `points_balance=10`;所有余额变动必须走计费层。 - 幂等要有数据库级兜底。MySQL 不支持条件唯一约束,不要用 partial unique;也不要用 `PointsLedger(user, change_type)` 这类会误伤其他流水类型的全局唯一约束。推荐独立 `SignupBonusGrant(user UNIQUE)` 标记表。 - 只对 T-608 上线后的新注册用户自动发放。历史用户是否补发不在本流程内,需单独审批、单独任务、单独批处理流水。 - 由于当前注册免邮箱验证,注册送点会提高刷号动机。T-608 已显式配置 `ACCOUNT_SIGNUP_RATE_LIMIT`(默认 `20/m/ip`)并复用 allauth signup rate limit;图形验证码 / 人机验证可作为上线前风控项继续增强。 @@ -502,7 +502,7 @@ CREATE TABLE image_generation_task_input ( | 配置热生效 | 后台改模型/密钥后运行时仍用旧值 | 不在进程内长缓存;每次查库或保存时失效缓存 | | 配置变更审计 | 改密钥/模型/别名映射无痕 | `AiConfigAuditLog` 自动记录后台 create/update/delete;密钥只记录 empty/set 变化,日志只读 | | API Key 泄露 | 明文存库一旦泄露全泄 | Key **哈希存储**(sha256),明文只在创建时显示一次,库内存 `key_hash`+`key_prefix`,鉴权做哈希比对 | -| 注册滥用 | 自助注册被批量刷以获取 100 点试用额度 | **免邮箱验证**(`ACCOUNT_EMAIL_VERIFICATION="none"`)下已显式配置 `ACCOUNT_SIGNUP_RATE_LIMIT` 基础注册限流;图形验证码 / 人机验证仍是上线前风控增强项;注册赠点走 `SignupBonusGrant(user UNIQUE)` 幂等标记 + `PointsLedger(signup_bonus)` 留痕,防重复发放 | +| 注册滥用 | 自助注册被批量刷以获取 10 点试用额度 | **免邮箱验证**(`ACCOUNT_EMAIL_VERIFICATION="none"`)下已显式配置 `ACCOUNT_SIGNUP_RATE_LIMIT` 基础注册限流;图形验证码 / 人机验证仍是上线前风控增强项;注册赠点走 `SignupBonusGrant(user UNIQUE)` 幂等标记 + `PointsLedger(signup_bonus)` 留痕,防重复发放 | | Web/API 抢 worker | 图片同步长请求占满 worker、拖慢用户端页面 | 单体下按路径把 `/api/v1/generate/*` 与用户端页面**分流到不同 gunicorn/worker 池**(见 5.1) | | 充错账户 | 扫码订单未绑定发起用户 | 订单创建即绑定 `user`;回调按 `order_no` 定位订单→其 user 入账 | | SSRF / 任意 URL 下载 | `image_url` 让服务端请求调用方指定地址 | 仅允许公网 http/https;请求前解析 IP 并拒绝内网/回环/链路本地/保留地址;重定向逐跳校验;流式读取并限制大小 | diff --git a/docs/05-coding-rules.md b/docs/05-coding-rules.md index e0f76d4..9097880 100644 --- a/docs/05-coding-rules.md +++ b/docs/05-coding-rules.md @@ -28,7 +28,7 @@ ## 3. 范围纪律 - MVP 只做 `02-requirements.md` 中列为 P0 的功能。 -- V2 / V3 功能(异步队列、报表、复杂活动赠点 / 邀请奖励、自动退款对账)只记录,不实现。(自助注册/扫码充值/API Key 管理已纳入 MVP;新用户一次性注册送 100 点已拆为 T-608) +- V2 / V3 功能(异步队列、报表、复杂活动赠点 / 邀请奖励、自动退款对账)只记录,不实现。(自助注册/扫码充值/API Key 管理已纳入 MVP;新用户一次性注册送 10 点已由 T-608 落地。) - 需求明确排除的非目标不得实现(自建收银台、多币种、分账等)。 - 不为“将来可能用到”提前抽象。 @@ -37,7 +37,7 @@ - 技术栈以 `03-tech-stack.md` 为准;新增依赖前先说明理由,未经确认不引入重量级依赖(Celery、Redis 等)。 - 模块职责以 `04-architecture.md` 为准,不跨层偷写逻辑。 - **点数余额只能经 `apps/billing` 修改**:余额存 `UserWallet`(与 auth `User` 分离),扣点锁 wallet 行;API 层/Admin/用户端都不得直接写 `points_balance`。 -- **注册赠点也只能经 `apps/billing` 修改**:T-608 的 100 点试用额度必须走 `grant_signup_bonus()` 这类计费层服务,写 `PointsLedger(signup_bonus)`,不得在 allauth adapter、view 或 signal 里直接把钱包余额设为 100。 +- **注册赠点也只能经 `apps/billing` 修改**:当前 10 点试用额度必须走 `grant_signup_bonus()` 这类计费层服务,写 `PointsLedger(signup_bonus)`,不得在 allauth adapter、view 或 signal 里直接把钱包余额设为 10。 - **API Key 哈希存储**(sha256),明文只在生成时返回一次;不存明文、任何页面不回显。 - **对外 API 只挂 API Key 认证,不挂 SessionAuthentication**;用户端页面用 session + CSRF;自助注册**免邮箱验证**(`ACCOUNT_EMAIL_VERIFICATION="none"`,邮箱仍必填且唯一)、生成接口须限流。 - **用户端与后台账号分离**:portal 与 `/admin/` 共用同一个 Django 会话(同一 `sessionid`),后台访问只多一道 `is_staff` 门槛。因此 **终端用户一律 `is_staff=False`**(普通用户登 portal 进不了后台,无泄露);**运营用专用 admin superuser 登录 `/admin/`,不要拿这个账号登 portal**——账号分离(方案 A)就是「解耦入口」的正解,不要为同账号双会话去拆 cookie/子域名。详见 `04-architecture.md`。 @@ -91,7 +91,7 @@ python manage.py runserver - **回调防护与兜底**:支付回调端点必须 `@csrf_exempt`(网关调用无 CSRF token);回调可能丢失,须提供按 `order_no` 主动查单兜底,避免已付款却一直 pending。 - **失败必退**:调用上游失败时,已预扣的点数必须冲正退回。 - **全程留痕**:每笔点数变动写 `points_ledger`,每次调用写 `call_record`,运营手工调整必须填原因。 -- **注册赠点幂等**:新用户 100 点试用额度必须有数据库级幂等保护,同一用户无论注册回调、重试或并发触发多少次,都最多发放一次;不得用 MySQL 不支持的条件唯一约束。 +- **注册赠点幂等**:新用户 10 点试用额度必须有数据库级幂等保护,同一用户无论注册回调、重试或并发触发多少次,都最多发放一次;不得用 MySQL 不支持的条件唯一约束。 - **敏感信息**:密钥/凭证只用环境变量或后台配置,不写真实值进代码与文档。 - 资金相关改动必须有对应测试(并发扣点、重复回调、上游失败退点至少各一条)。 - 不绕过支付系统的鉴权、验签或风控。 diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 983e2ac..e77282e 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -86,7 +86,7 @@ | 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 | DONE | | T-606 | 公开首页 + 客户端下载入口 | T-501 | 给网站补「前门」并提供桌面端下载。**路由改造**:`/` 从「重定向到 `/dashboard`」改为**公开首页**(匿名可访问、不跳登录);已登录用户显示「进入控制台」,匿名显示「注册/登录 + 下载客户端」。**首页内容**(SSR 模板):项目一句话介绍(生成标题/图片、按点数计费)+ 三步上手(注册→充值→建 API Key→桌面端填 Key)+ 下载入口 + 文档链接。**`DownloadRelease` 模型**:`platform`(如 windows)、`version`、`file`(FileField,**前期存 `MEDIA_ROOT`、服务器托管**) + `external_url`(URLField 可选,**后续切对象存储/CDN 用,有则优先**)、`sha256`、`is_current`(每平台仅一个当前版本)、`release_notes`、时间戳;admin 可上传安装包并标记当前版本;迁移。**下载区块**:展示当前 release 的版本、下载按钮、**SHA256 校验值**、可选 release notes;无 current release 时优雅提示「暂未发布」。**托管策略**:**前期安装包放本服务器**(生产由 **Nginx 直接服务 media/下载文件、不走 Django**,与 T-403 static/media serving 一致,大文件不占 gunicorn worker);`external_url` 预留,后续切对象存储只改后台链接不改代码。**安全**:下载走 HTTPS,页面展示 SHA256 供校验;**代码签名**作为决策登记——未签名 Windows 安装包会被 SmartScreen 拦「未知发布者」、macOS 被 Gatekeeper 拦,首页先给「如何忽略警告」说明,正式签名后续补(挂 Backlog / deployment)。**含测试**:`/` 匿名 200 不跳登录、下载区展示当前 release、无 current release 优雅处理、已登录用户显示「进入控制台」。**视觉原型(已定 v1)**:按 `prototypes/cmhub-homepage-v1.svg` 落地——「生成台」方向:靛蓝=生成 / 琥珀=点数;Hero 为「商品图 + 一句话 → 吸睛标题 + 生成主图 + 点数计量」转化图;四步上手 01–04、两张能力卡、深色计费 band、下载区(版本/SHA256/未签名提示);配色、间距、结构照此原型转成 Django 模板(Bootstrap + 本地 static)。**视觉一致性(brand token)**:按 [`brand.md`](brand.md) 抽出共享 CSS 变量(如 `apps/portal/static/portal/brand.css`),**首页与现有 portal 页面(dashboard/记录/充值/API Key)一起套用同一套 token**(把配色/字体变量灌进现有 Bootstrap,各页只引用变量不散写 hex),确保落地页与登录后控制台风格一致——**不是只做漂亮首页**。验收 `check`/`test`/`init` 全绿并在 `../progress.md` 留证据 | DONE | | T-607 | 桌面端最新版本检查接口 | T-606 | 给桌面端自动检查更新提供只读 JSON 合约。**新增路由**:`GET /api/v1/client/releases/latest?platform=windows`,公开匿名可访问,**不需要 API Key、不读取用户、不扣点、不占用生成接口限流**;只返回发布元数据,不返回本地文件系统路径、后台 ID、内部状态或任何用户数据。**请求参数**:`platform` 支持 `windows` / `macos` / `linux`,缺省按 `windows`;非法平台返回 `400 bad_request`。**响应结构**:有当前版本时返回 `{platform, release:{version, download_url, sha256, release_notes, published_at}}`;无当前版本或当前版本没有下载地址时返回 `{platform, release:null, message:"暂未发布"}` 且 HTTP 200,方便客户端安静处理。**数据来源**:复用 `DownloadRelease`,只查 `platform + is_current=True`;`download_url` 继续按 `external_url` 优先,否则由 `file.url` 生成绝对 HTTPS URL;`published_at` 可先使用 `updated_at`。**缓存与安全**:可加短 TTL 公共缓存(如 60 秒);生产下载仍走 HTTPS + SHA256 校验;不得把 `MEDIA_ROOT` 或服务器路径暴露给客户端。**含测试**:匿名无 Key 可访问;当前 release 返回完整结构和绝对下载 URL;无 current release 返回 `release:null`;非法 platform 返回 400;`external_url` 优先于 `file`;响应不含本地路径、模型/密钥/用户字段;`check`/目标测试通过并在 `../progress.md` 留证据 | DONE | -| T-608 | 新用户注册赠送 100 点试用点数 | T-501, T-203, T-401 | 把 2026-07-08 的产品口径落地为账本安全实现。**业务口径**:只对 T-608 上线后的新注册用户自动发放 100 点;历史用户是否补发不在本任务内,需单独审批和批处理任务。**数据与服务**:新增 `PointsLedger.ChangeType.SIGNUP_BONUS`(展示为注册赠点)和 MySQL 兼容的数据库级幂等兜底 `SignupBonusGrant(user UNIQUE)`,不要用 partial unique / 条件唯一约束;新增 `apps.billing.services.grant_signup_bonus(user, points=100)`,在事务内锁/创建钱包、发放点数、写 `PointsLedger(signup_bonus,+100,balance_after,reason)`,重复调用或并发触发不得重复发放。**接入点**:allauth adapter 注册成功后调用 billing 服务,不得在 portal 直接写 `points_balance`;失败要清晰暴露并保持注册 / 账务一致性。**页面与文案**:公开首页、注册页、dashboard/记录页等用户端文案同步「注册送 100 点」;点数记录页能展示注册赠点正向流水;admin 可检索该流水和注册赠点记录。**防刷**:免邮箱验证策略下显式配置 `ACCOUNT_SIGNUP_RATE_LIMIT`(默认 `20/m/ip`),由 allauth signup rate limit 执行;图形验证码 / 人机验证仍作为上线前风控增强项。**测试**:注册后余额为 100 且有一条 `signup_bonus` 流水;重复调用服务不重复加点;并发触发不重复发放;现有用户不会自动补发;dashboard / 点数记录显示一致;`check`/迁移/目标测试通过并在 `../progress.md` 留证据 | DONE | +| T-608 | 新用户注册赠送试用点数(当前 10 点) | T-501, T-203, T-401 | 已完成:仅对 T-608 上线后的新注册用户,经 `apps.billing.services.grant_signup_bonus(user, points=10)` 一次性发放 10 点;历史用户不补发。服务在事务内锁/创建钱包、写 `SignupBonusGrant(user UNIQUE)` 幂等记录和 `PointsLedger(signup_bonus,+10,balance_after,reason)`,重复调用或并发触发不重复发放;allauth adapter 只调用 billing 服务,不直接改余额。公开首页、注册页、dashboard/点数记录页展示当前 10 点,admin 可检索记录;免邮箱验证下由 `ACCOUNT_SIGNUP_RATE_LIMIT=20/m/ip` 限制注册频率。已覆盖注册余额/流水、重复调用、并发幂等、页面文案和后台检索;当前默认值由 `billing.0009_signup_bonus_default_ten` 与服务默认参数共同保证。 | DONE | | T-609 | 桌面端版本检查接口增加强制更新标记 | T-607 | 在 `GET /api/v1/client/releases/latest?platform=windows` 的当前版本响应中,为 `release` 对象新增布尔字段 `force_update`,用于桌面端判断是否必须升级。**接口兼容**:只新增字段,不删除既有 `version` / `download_url` / `sha256` / `release_notes` / `published_at` 字段;无当前版本或无下载地址时仍返回 `release:null`,不返回 `force_update`。**数据来源**:在 `DownloadRelease` 增加 `force_update` 布尔字段,默认 `false`;django-admin 可编辑并在列表展示。**示例**:`{platform:"windows", release:{version:"0.1.1", download_url:"...0.1.1.zip", release_notes:"优化了ai模块的生图的功能", force_update:true}}`。**安全**:接口仍公开匿名只读,不读取用户、不扣点、不暴露后台 ID、本地文件路径或内部状态。**文档**:同步 `api.md`、`routes.md`、`04-architecture.md`、`current-state.md`、版本接口对接文档口径。**测试**:覆盖 `force_update=true` / 默认 `false`、无 current release 不返回该字段、响应字段白名单更新、admin 字段可见;`makemigrations` / `migrate` / `check` / 目标测试通过并在 `../progress.md` 留证据 | DONE | | T-610 | 首页导入模板下载入口 | T-606 | 在公开首页“下载客户端”按钮右侧新增“下载导入模板”链接,让用户下载桌面端导入商品数据使用的 Excel 模板。**产品口径**:模板下载是公开资源,匿名可见;不需要登录、不需要 API Key、不扣点、不占用生成接口限流。**后台配置**:新增独立 `ImportTemplate`(不要复用 `DownloadRelease`,两者生命周期不同),字段建议包含 `name`、`file`(FileField,`MEDIA_ROOT/import_templates/`)、`external_url`(可选,有则优先,后续切 CDN/对象存储)、`sha256`(可选)、`is_current`、`notes`、`created_at`、`updated_at`;admin 可上传/更新模板并标记当前模板;每次保存 current 时把其他 current 置为 false,避免使用 MySQL 不支持的条件唯一约束。**首页行为**:`HomeView` 同时读取 Windows 当前 `DownloadRelease` 和当前 `ImportTemplate`;有当前模板且有可下载地址时,在“下载客户端”右侧展示“下载导入模板”链接;无模板时不显示该链接或显示不可用提示,不能影响客户端下载入口和首页 200。**托管策略**:本地上传模板复用生产 Nginx `/media/` 静态托管,不经 Django/Gunicorn 传大文件;`external_url` 优先于 `file.url`,响应/页面不得暴露本地 `MEDIA_ROOT` 或服务器文件路径。**UI**:保持 T-606 下载区现有布局,桌面端按钮为主按钮,导入模板为次级链接/按钮;移动端需换行不重叠。**文档**:同步 `02-requirements.md`、`04-architecture.md`、`routes.md`、`api.md`、`current-state.md`。**测试**:覆盖首页有模板时展示链接且 href 为绝对/可访问 media URL,`external_url` 优先,本地路径不泄露,无模板时首页正常且不显示错误;admin 字段可见;`makemigrations` / `migrate` / `check` / 目标测试通过并在 `../progress.md` 留证据 | DONE | | T-611 | 用户端品牌名统一为“虾皮圈” | T-606 | 把用户端页面上对外展示的项目/产品名从 `cmhub` 统一改为“虾皮圈”。**范围**:只改终端用户可见的 portal 页面文案和浏览器标题,包括公开首页、顶部导航品牌、注册/登录/登出页、控制台、充值、API Key、可用模型、充值记录、点数记录等模板中作为品牌/产品名出现的 `cmhub`;用户端文案里的“cmhub API Key”应改为“虾皮圈 API Key”。**不改**:仓库名、Python 包名、Django app 名、数据库表名、环境变量、API 路径、域名、对外接口字段、后台内部模型名和技术文档里指代服务代号的 `cmhub`。**文档**:同步 `02-requirements.md`、`00-ai-start-here.md`、`current-state.md` 与 `../progress.md`。**测试**:更新 portal 相关断言,覆盖首页展示“虾皮圈”且不再展示旧标题 `cmhub AI 电商生成台`;关键用户端页面渲染不含作为品牌展示的 `cmhub`;`check`、`makemigrations --check --dry-run`、目标 portal 测试通过并在 `../progress.md` 留证据 | DONE | @@ -123,6 +123,7 @@ ## 待办池(Backlog) - ~~异步生成(任务队列 + 轮询/回调)~~ → 已立项 **T-612~T-615**(生图异步任务化:先硬截止 + 长请求池校准止血,再抽共享 core,最后新增 `/tasks` 提交+轮询接口与旧同步接口共存、旧路遥测后弃用)。 +- **T-621 注册赠点运营后台配置**:将当前固定 10 点改为 admin 可管理的单例策略,包含策略审计、停用时的 0 点决定记录及不追溯历史用户。该项涉及账本与后台配置,现移入 Backlog,暂不实施。 - 按账号授权可用别名(`account_alias_permission`),防止调用未授权/昂贵模型。 - 别名按比例分流到多个模型(灰度 / A/B / 故障转移);供应商 A 故障自动切 B。 - 异步生图提交时模型配置快照:当前 worker 会复跑 `prepare_generation()`,重新审核 prompt、解析别名 / Provider / 定价,但账务使用已预扣 `CallRecord.points_cost`。短队列下可接受;若后续队列积压或后台频繁切换别名,应在 submit 阶段快照 `ai_model_id` / `model_used` / 必要 Provider 配置,worker 使用提交时模型执行,同时保留 prompt 复审。 diff --git a/docs/README.md b/docs/README.md index 3d1476d..d9c9468 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,7 +24,7 @@ - [Phase 1 AI 层审核](phase-1-review.md):T-101~104 代码审核结论与修补清单(P1/P2/P3),对应任务 T-105;重点提示图片同步风险未退与 `parameters` 越权计费隐患。 - [Phase 2 计费核心审核](phase-2-review.md):T-201~203 代码审核结论(资金安全逐条核对)与加固清单(**零 P1**,P2/P3),对应任务 T-204。 - [Phase 3 对外 API 与充值审核](phase-3-review.md):T-301~305 代码审核结论(鉴权/回调/幂等/入账逐条核对)与优化建议,对应任务 T-306;**P1 提示 `image_url` SSRF 上线前必修**。 -- [Phase 4 用户端审核](phase-4-review.md):T-501~504 代码审核结论(按当时口径核对注册不送点/越权/CSRF/明文只显一次/到账以回调为权威;2026-07-08 新需求已由 T-608 改为注册送 100 点)与优化建议(**零 P1**,P2/P3),对应任务 T-505。 +- [Phase 4 用户端审核](phase-4-review.md):T-501~504 代码审核结论(按当时口径核对注册不送点/越权/CSRF/明文只显一次/到账以回调为权威;2026-07-08 新需求曾由 T-608 改为注册送 100 点,当前额度为 10 点)与优化建议(**零 P1**,P2/P3),对应任务 T-505。 - [MVP 完整验收报告](mvp-acceptance.md):T-402 对 `02-requirements.md` P0 验收项的逐项结论、测试证据和已知限制。 - [部署 / 运行文档](deployment.md):T-403 生产部署步骤、宝塔 / Nginx / Gunicorn 配置、静态文件、共享 cache、图片超时与上线检查。 - [API 合约](api.md):对外接口、支付回调、AI 调用模块合约、错误码。 diff --git a/docs/api.md b/docs/api.md index b202d4b..ea802ca 100644 --- a/docs/api.md +++ b/docs/api.md @@ -15,7 +15,7 @@ - 例外:客户端下载版本检查接口只返回公开发布元数据,设计为匿名只读接口,不需要 API Key,不读取用户、不扣点。 - 图片生成提供两条并存路径:旧 `POST /api/v1/generate/image` 为同步接口,可能耗时较长;T-614 起新增异步任务接口 `POST /api/v1/generate/image/tasks` + `GET /api/v1/generate/image/tasks/{task_id}`,新版桌面端优先使用异步提交 / 轮询,旧客户端继续走同步接口。 - 用户端注册使用同一个 `User` 账本主体;注册邮箱**必填且唯一**(`ACCOUNT_EMAIL_VERIFICATION="none"`,**不做邮箱验证**、注册即可用),唯一约束避免同邮箱对应多个点数账户。 -- T-608 起新用户注册成功一次性赠送 **100 点**试用点数;赠点必须经计费层写入钱包和 `PointsLedger(change_type=signup_bonus)`,不得直接改余额字段。历史用户是否补发不属于默认注册流程。 +- T-608 起新用户注册成功一次性赠送 **10 点**试用点数;赠点必须经计费层写入钱包和 `PointsLedger(change_type=signup_bonus)`,不得直接改余额字段。历史用户是否补发不属于默认注册流程。 - API Key 库内只存 `key_hash`(SHA-256)与 `key_prefix`,明文只在创建时返回一次,不在 admin、日志或调用记录中回显。 - 每次生成调用写 `CallRecord`;只允许保存 `result_ref` / `result_summary` 这类引用或摘要,不保存 provider `raw`、base64 图片或敏感上游字段。 @@ -41,7 +41,7 @@ T-306 已实现对外 API 安全加固:`image_url` 下载只允许 `http` / `h T-604 目标口径:生成接口在 serializer 基础校验后先执行 prompt 本地敏感词检查;命中返回 `400 content_blocked`,且不得下载 `image_url`、不得预扣点、不得写 `CallRecord` / `PointsLedger`、不得调用上游。T-604 不启用输出审核和图片审核;详细规则见 [`moderation.md`](moderation.md)。 -T-501/T-608 已实现用户端注册 / 登录基线:`/signup` `/login` `/logout` 走 django-allauth + Django session + CSRF;**免邮箱验证、注册即可用(`ACCOUNT_EMAIL_VERIFICATION="none"`,邮箱仍必填且唯一)**。注册成功后 allauth adapter 调用 `grant_signup_bonus()` 一次性发放 100 点注册试用额度,并创建 `signup_bonus` 流水;注册限流由 allauth signup rate limit 执行,项目显式配置 `ACCOUNT_SIGNUP_RATE_LIMIT`(默认 `20/m/ip`)。对外 API 仍只认 API Key,不接受 Web session。(免验证策略见 T-605 与 2026-07-06 决策) +T-501/T-608 已实现用户端注册 / 登录基线:`/signup` `/login` `/logout` 走 django-allauth + Django session + CSRF;**免邮箱验证、注册即可用(`ACCOUNT_EMAIL_VERIFICATION="none"`,邮箱仍必填且唯一)**。注册成功后 allauth adapter 调用 `grant_signup_bonus()` 一次性发放 10 点注册试用额度,并创建 `signup_bonus` 流水;注册限流由 allauth signup rate limit 执行,项目显式配置 `ACCOUNT_SIGNUP_RATE_LIMIT`(默认 `20/m/ip`)。对外 API 仍只认 API Key,不接受 Web session。(免验证策略见 T-605 与 2026-07-06 决策) T-502 已实现用户端 API Key 自助管理基线:`/apikeys` 走 Django session + CSRF;登录用户可生成和删除自己的 Key,生成后的明文只在重定向后的首个页面显示一次,库内只保存 `key_hash` 与 `key_prefix`。用户端“删除”落库为 `revoked`,保留历史记录关联;吊销后的 Key 调用生成 / 余额接口返回 `403 account_disabled`,缺失、无效或不存在的 Key 仍返回 `401 unauthorized`。 @@ -475,7 +475,7 @@ calculate_points_cost(operation_type: str, alias: str, resolution: str | None = quote_recharge_points(amount, currency: str = "CNY", at=None) -> RechargeQuote calculate_points_granted(amount, currency: str = "CNY", at=None) -> int create_recharge_order(..., user, amount, pay_method: str, currency: str = "CNY") -> RechargeOrder -grant_signup_bonus(user, points: int = 100) -> SignupBonusGrantResult +grant_signup_bonus(user, points: int = 10) -> SignupBonusGrantResult precharge_call(..., user, points_cost: int, operation_type: str, alias: str, ...) -> CallCharge mark_call_success(call_record: CallRecord, ...) -> CallRecord refund_call_points(call_record: CallRecord, ...) -> RefundResult @@ -490,7 +490,7 @@ query_and_apply_recharge_payment(order_no: str, query_func) -> RechargeResult - 缺计费规则抛 `NoPricingRuleError(code="no_pricing_rule")`,API 层应翻译为上方同名错误码。 - `ExchangeRate` 按 `currency + effective_from` 取当前 active 汇率;充值下单时应锁定当时的 `exchange_rate` / `points_granted` 到订单,回调入账不得按新汇率重算。 - 金额换点数采用 `floor(amount * points_per_unit)`,点数为整数。 -- `grant_signup_bonus()` 是注册赠点唯一入口:只对当前用户首次成功发放 100 点,锁定 / 创建 `UserWallet`,写 `PointsLedger(change_type=signup_bonus, points_delta=+100)`,并用 MySQL 兼容的唯一幂等标记防重复发放。 +- `grant_signup_bonus()` 是注册赠点唯一入口:只对当前用户首次成功发放 10 点,锁定 / 创建 `UserWallet`,写 `PointsLedger(change_type=signup_bonus, points_delta=+10)`,并用 MySQL 兼容的唯一幂等标记防重复发放。 - `create_recharge_order()` 创建 `RechargeOrder(status=pending)` 并绑定发起用户;写入订单创建时的金额、币种、汇率、预计点数,再调用支付网关下单取二维码票据并回填 `code_url` / `expires_at`;支付平台下单失败时订单标记 `failed`。 - `precharge_call()` 使用事务 + `select_for_update()` 锁 `UserWallet` 行;余额不足抛 `InsufficientPointsError(code="insufficient_points")`,不创建 `CallRecord`、不写 `PointsLedger`、不调上游。 - 预扣成功后写 `CallRecord(status=pending)` 与 `PointsLedger(change_type=consume, points_delta=-N)`;上游成功只更新调用记录,余额不再变化。 diff --git a/docs/brand.md b/docs/brand.md index c638a92..f218ed5 100644 --- a/docs/brand.md +++ b/docs/brand.md @@ -79,7 +79,7 @@ cmhub 有双重身份——**AI 生成** + **按点数计费**。视觉主线 ## 5. 语气(Voice) - **说人话**:按用户看得懂的说,不用系统术语(说「管理 API Key」,不说「凭证配置」)。 -- **诚实**:如实说「注册送 100 点试用点数」「失败自动退点」「首次安装点『仍要运行』」——不粉饰。 +- **诚实**:如实说「注册送 10 点试用点数」「失败自动退点」「首次安装点『仍要运行』」——不粉饰。 - **动词优先、句子式大小写**:按钮说清「做完会发生什么」(「下载 .exe」而非「获取」)。 - **同一动作全程同名**:按钮「充值」→ 结果提示也用「充值」。 diff --git a/docs/current-state.md b/docs/current-state.md index 7dac0d4..a995fab 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -11,10 +11,10 @@ ## 当前快照 -- 日期:2026-07-16 -- 阶段: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 字段级中文化、T-604 中文敏感词本地过滤、T-605 免邮箱验证策略落地、T-606 公开首页 + 客户端下载入口、T-607 桌面端最新版本检查接口、T-608 新用户注册赠送 100 点试用点数、T-609 桌面端版本检查接口增加强制更新标记、T-610 首页导入模板下载入口、T-611 用户端品牌名统一为虾皮圈、T-612 生图同步接口止血、T-613 抽生成核心 service、T-614 生图异步任务化接口、T-615 旧同步生图接口遥测 / 弃用口径、T-616 生图失败自动重试 2 次、T-617 桌面端版本文件大小字段与 T-618 客户端发布版本后台必填校验元数据已完成;后续仍需处理真实支付回调到账闭环、客户端下载包发布和生产侧旧同步接口用量观察 +- 日期:2026-07-18 +- 阶段: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 字段级中文化、T-604 中文敏感词本地过滤、T-605 免邮箱验证策略落地、T-606 公开首页 + 客户端下载入口、T-607 桌面端最新版本检查接口、T-608 新用户注册赠送试用点数(当前 10 点)、T-609 桌面端版本检查接口增加强制更新标记、T-610 首页导入模板下载入口、T-611 用户端品牌名统一为虾皮圈、T-612 生图同步接口止血、T-613 抽生成核心 service、T-614 生图异步任务化接口、T-615 旧同步生图接口遥测 / 弃用口径、T-616 生图失败自动重试 2 次、T-617 桌面端版本文件大小字段与 T-618 客户端发布版本后台必填校验元数据已完成;后续仍需处理真实支付回调到账闭环、客户端下载包发布和生产侧旧同步接口用量观察 - 技术栈:系统 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 + ahocorapy 1.6.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/T-608 已接入 allauth 注册登录路径,注册成功后 adapter 调用 `grant_signup_bonus()` 经 billing 一次性发放 100 点并写 `signup_bonus` 流水,新增 `SignupBonusGrant(user UNIQUE)` 幂等标记、admin 只读检索和 `billing.0007` 迁移;T-502 已新增 `/apikeys`、API Key 创建表单、列表页和删除(吊销)动作,生成后明文只显示一次,列表只显示 prefix;T-503/T-608 已扩展 `/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 环境变量与 `ACCOUNT_SIGNUP_RATE_LIMIT` 注册限流配置;T-601 已新增 `apps.ai.catalog.get_public_model_catalog()`、`GET /api/v1/models` 与 portal `/models` 只读页面,只展示 active 可调用别名、能力、是否需要原图和点数单价,不解密 provider key,不暴露底层 SKU / URL / key / `extra_body`;T-604 已新增 `apps.moderation`、`SensitiveWord` 模型/admin/迁移、keyword provider、归一化管线和共享 cache 版本失效,生成接口已改为 prompt 先审再读取图片/计费/扣点/调上游;T-606 已新增公开首页 `/`、`DownloadRelease` 模型/admin/迁移、首页 SSR 模板、共享 `portal/brand.css`,并把现有 portal 页面套入同一套品牌 token;T-607/T-609 已新增 `ClientLatestReleaseView` 与 `/api/v1/client/releases/latest`,公开匿名返回当前客户端版本 JSON,`release.force_update` 表示该版本是否强制升级;`portal.0002_downloadrelease_force_update` 已给 `DownloadRelease` 增加 `force_update` 字段,admin 可编辑和筛选;T-610 已新增 `ImportTemplate` 模型/admin/迁移 `portal.0003_importtemplate`,首页读取当前模板并在“下载客户端”旁展示“下载导入模板”,本地文件 URL 转为当前站点绝对 URL,`external_url` 优先;T-611 已把用户端 portal 可见品牌名统一为“虾皮圈”,包括页面标题、顶部导航、首页 H1、用户端“虾皮圈 API Key”文案和 allauth 邮件模板;T-612 已新增 `AI_IMAGE_UPSTREAM_DEADLINE_SECONDS`,生图 Provider 上游请求和上游返回图片 URL 下载会按 `min(AiModel.timeout_seconds 或分辨率默认值, 硬截止)` 控制读取超时,超时返回 `upstream_timeout` 并走既有失败退点路径;T-613 已把旧同步生成链路抽成 `GenerationInput`、`prepare_generation()`、`precharge_generation()`、`execute_precharged_generation()` 与 `GenerationResult`,旧 view 只负责 serializer 和异常转 HTTP,后续异步 worker 可复用已预扣执行 / 确认 / 退点阶段。 +- 生产代码:已有最小 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/T-608 已接入 allauth 注册登录路径,注册成功后 adapter 调用 `grant_signup_bonus()` 经 billing 一次性发放 10 点并写 `signup_bonus` 流水,新增 `SignupBonusGrant(user UNIQUE)` 幂等标记、admin 只读检索和 `billing.0007` 迁移;T-502 已新增 `/apikeys`、API Key 创建表单、列表页和删除(吊销)动作,生成后明文只显示一次,列表只显示 prefix;T-503/T-608 已扩展 `/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 环境变量与 `ACCOUNT_SIGNUP_RATE_LIMIT` 注册限流配置;T-601 已新增 `apps.ai.catalog.get_public_model_catalog()`、`GET /api/v1/models` 与 portal `/models` 只读页面,只展示 active 可调用别名、能力、是否需要原图和点数单价,不解密 provider key,不暴露底层 SKU / URL / key / `extra_body`;T-604 已新增 `apps.moderation`、`SensitiveWord` 模型/admin/迁移、keyword provider、归一化管线和共享 cache 版本失效,生成接口已改为 prompt 先审再读取图片/计费/扣点/调上游;T-606 已新增公开首页 `/`、`DownloadRelease` 模型/admin/迁移、首页 SSR 模板、共享 `portal/brand.css`,并把现有 portal 页面套入同一套品牌 token;T-607/T-609 已新增 `ClientLatestReleaseView` 与 `/api/v1/client/releases/latest`,公开匿名返回当前客户端版本 JSON,`release.force_update` 表示该版本是否强制升级;`portal.0002_downloadrelease_force_update` 已给 `DownloadRelease` 增加 `force_update` 字段,admin 可编辑和筛选;T-610 已新增 `ImportTemplate` 模型/admin/迁移 `portal.0003_importtemplate`,首页读取当前模板并在“下载客户端”旁展示“下载导入模板”,本地文件 URL 转为当前站点绝对 URL,`external_url` 优先;T-611 已把用户端 portal 可见品牌名统一为“虾皮圈”,包括页面标题、顶部导航、首页 H1、用户端“虾皮圈 API Key”文案和 allauth 邮件模板;T-612 已新增 `AI_IMAGE_UPSTREAM_DEADLINE_SECONDS`,生图 Provider 上游请求和上游返回图片 URL 下载会按 `min(AiModel.timeout_seconds 或分辨率默认值, 硬截止)` 控制读取超时,超时返回 `upstream_timeout` 并走既有失败退点路径;T-613 已把旧同步生成链路抽成 `GenerationInput`、`prepare_generation()`、`precharge_generation()`、`execute_precharged_generation()` 与 `GenerationResult`,旧 view 只负责 serializer 和异常转 HTTP,后续异步 worker 可复用已预扣执行 / 确认 / 退点阶段。 - T-614/T-616 生产代码补充:已新增 `ImageGenerationTask` 与 `api.0001_initial`,新增 `POST /api/v1/generate/image/tasks`、`GET /api/v1/generate/image/tasks/{task_id}`、`apps.api.image_tasks` 任务服务、`run_image_tasks` management command 和只读 admin;异步提交支持 `Idempotency-Key` 去重 / 冲突检测,worker 使用 DB 任务表、租约、心跳与 reaper,成功返回 cmhub 托管 URL;T-616 已通过 `api.0002_imagegenerationtask_next_attempt_at_and_more` 增加 `next_attempt_at` 与 `(status, next_attempt_at)` 索引,临时性 `upstream_timeout` / `upstream_error` 默认最多重试 2 次,重试期间不退点,最终失败 / 僵任务才走计费层幂等退款。 - T-617 生产代码补充:`DownloadRelease` 已新增 `size_bytes` 可空正整数字段和迁移 `portal.0004_downloadrelease_size_bytes`,django-admin 可填写并在列表展示;`GET /api/v1/client/releases/latest` 的 `release` 对象新增 `size_bytes`,有值返回整数,未配置返回 `null`,未发布时仍只返回 `release:null`。 - T-618 生产代码补充:`DownloadReleaseAdmin` 已挂专用 `ModelForm`,后台新增 / 编辑客户端发布版本时 `sha256` 与 `size_bytes` 必填;数据库字段仍兼容历史空值,API 仍可读取历史 `size_bytes=null` 记录。 @@ -36,7 +36,7 @@ - 最新验证:T-612 生图同步接口止血已验证 `py -3.12 -m py_compile config\settings.py apps\ai\providers\utils.py apps\ai\providers\openai_compatible.py apps\api\generation.py apps\ai\tests.py apps\api\tests.py` 通过;`py -3.12 manage.py check` 通过,0 issues;`py -3.12 manage.py makemigrations --check --dry-run` 通过,No changes detected;生图 Provider 目标测试 3 tests OK,确认生图上游请求与返回图片 URL 下载会被 `AI_IMAGE_UPSTREAM_DEADLINE_SECONDS` 截断,文本生成不受该硬截止影响;`GenerateApiTests.test_image_upstream_timeout_refunds_precharged_points_and_marks_call_failed` 通过;`GenerateApiTests` 整组 17 tests OK;`.\init.ps1` 收尾通过;`git diff --check` 通过,仅 Windows CRLF 提示。测试期仍保留 allauth 在 MySQL 条件唯一约束上的既有 `models.W036` 警告。 - 最新验证:T-611 用户端品牌名统一为虾皮圈已验证 `py -3.12 manage.py check` 通过,0 issues;`py -3.12 manage.py makemigrations --check --dry-run` 通过,No changes detected;`git diff --check` 通过,仅 Windows CRLF 提示;`py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests.test_homepage_is_public_and_shows_anonymous_onboarding_without_release apps.portal.tests.PortalAccountFlowTests.test_homepage_shows_current_download_release apps.portal.tests.PortalAccountFlowTests.test_portal_pages_use_shopee_circle_branding --keepdb --noinput --verbosity 2` 通过,3 tests OK。测试期仍保留 allauth 在 MySQL 条件唯一约束上的既有 `models.W036` 警告。 - 测试: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-608 新用户注册赠送 100 点试用点数已验证 `py -3.12 -m py_compile config\settings.py apps\billing\models.py apps\billing\services.py apps\billing\admin.py apps\portal\adapters.py apps\portal\views.py apps\billing\tests.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 migrate billing --noinput` 通过,No migrations to apply;注册赠点服务和并发幂等目标测试 3 tests OK;注册限流配置测试 1 test OK;portal 注册赠点、首页文案、导航、dashboard 和点数记录目标测试 6 tests OK;`SignupBonusGrant` admin 注册 smoke 1 test OK;`.\init.ps1` 收尾通过(Python 3.12.3,依赖已满足,`manage.py check` 0 issues,打印启动命令);`git diff --check` 通过,仅 Windows CRLF 提示。测试期仍有 allauth 在 MySQL 条件唯一约束上的既有 `models.W036` 警告;一次 portal 目标测试启动阶段曾因远程 MySQL 连接 `43.128.3.240` 超时中断,端口探测恢复后重跑通过。 +- 最新验证:T-608 新用户注册赠送试用点数(当前 10 点)已验证 `py -3.12 -m py_compile config\settings.py apps\billing\models.py apps\billing\services.py apps\billing\admin.py apps\portal\adapters.py apps\portal\views.py apps\billing\tests.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 migrate billing --noinput` 通过,No migrations to apply;注册赠点服务和并发幂等目标测试 3 tests OK;注册限流配置测试 1 test OK;portal 注册赠点、首页文案、导航、dashboard 和点数记录目标测试 6 tests OK;`SignupBonusGrant` admin 注册 smoke 1 test OK;`.\init.ps1` 收尾通过(Python 3.12.3,依赖已满足,`manage.py check` 0 issues,打印启动命令);`git diff --check` 通过,仅 Windows CRLF 提示。测试期仍有 allauth 在 MySQL 条件唯一约束上的既有 `models.W036` 警告;一次 portal 目标测试启动阶段曾因远程 MySQL 连接 `43.128.3.240` 超时中断,端口探测恢复后重跑通过。 - 最新验证:T-607 桌面端最新版本检查接口已验证 `.\init.ps1` 开工前通过;`py -3.12 -m py_compile apps\api\views.py apps\api\urls.py apps\api\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.api.tests.ClientLatestReleaseApiTests --keepdb --noinput --verbosity 2` 通过,9 tests OK;`py -3.12 manage.py test apps.api.tests.ApiKeyAuthenticationTests apps.api.tests.BalanceApiTests apps.api.tests.ModelsCatalogApiTests apps.api.tests.ClientLatestReleaseApiTests --keepdb --noinput --verbosity 2` 通过,25 tests OK;`py -3.12 manage.py test apps.api --keepdb --noinput --verbosity 1` 通过,54 tests OK;`.\init.ps1` 收尾通过;`git diff --check` 通过,仅 Windows CRLF 提示。测试期仅保留 allauth 在 MySQL 条件唯一约束上的既有 `models.W036` 警告。 - 最新验证:T-606 公开首页 + 客户端下载入口已验证 `.\init.ps1` 通过(Python 3.12.3,依赖已满足,`manage.py check` 0 issues,打印启动命令);`py -3.12 -m py_compile apps\portal\models.py apps\portal\admin.py apps\portal\views.py apps\portal\urls.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 migrate portal` 已应用 `portal.0001_initial` 到当前数据库;`py -3.12 manage.py shell -c "... Client().get('/') ..."` 返回 `STATUS=200`、首页标识和下载区标识均存在;`py -3.12 manage.py findstatic portal/brand.css portal/vendor/bootstrap/bootstrap.min.css portal/vendor/qrcode/qrcode.js --verbosity 1` 均找到;`py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests.test_homepage_is_public_and_shows_anonymous_onboarding_without_release apps.portal.tests.PortalAccountFlowTests.test_homepage_shows_current_download_release apps.portal.tests.PortalAccountFlowTests.test_homepage_shows_dashboard_entry_for_authenticated_user apps.portal.tests.PortalAccountFlowTests.test_download_release_only_keeps_one_current_per_platform apps.portal.tests.PortalAccountFlowTests.test_authenticated_nav_highlights_current_page_only --noinput --verbosity 2` 通过,5 tests OK。`py -3.12 manage.py test apps.portal.tests.PortalAccountFlowTests --keepdb --noinput --verbosity 2` 已重跑,因远程 MySQL `43.128.3.240` 在测试库迁移阶段连接超时(2003 / WinError 10060)中断,不是断言失败;数据库恢复后需补跑 portal 整组。 - 最新验证: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 稳定性阻塞。 @@ -50,7 +50,7 @@ - 数据:AI 上游调用与模型配置参考 `D:\chengma\cmbot`(`src/services/ai_text_service.py`、`ai_image_service.py`、`config/ai_models.json`);真实 `ai_models.json` 不提交,需通过 `import_ai_models` 命令加密导入 - 标准启动路径: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 适配器**对外提供,不暴露具体供应商模型;自助扫码充值;新用户注册成功一次性赠送 100 点试用点数,必须经 billing 写 `signup_bonus` 流水,且通过 `SignupBonusGrant(user UNIQUE)` 防重复发放。详见 `04-architecture.md` 与 2026-07-08 的 `progress.md` 决策 +- 设计基线:**自助用户端 + 对外 API + 运营后台**三合一单体;用户模型 `User`(auth)/`UserWallet`(点数,锁 wallet 扣点)/`ApiKey`(1:N,哈希存储);生成标题、生成图片和多图理解均通过**能力别名 + Provider 适配器**对外提供,不暴露具体供应商模型;自助扫码充值;新用户注册成功一次性赠送 10 点试用点数,必须经 billing 写 `signup_bonus` 流水,且通过 `SignupBonusGrant(user UNIQUE)` 防重复发放。详见 `04-architecture.md` 与 2026-07-08 的 `progress.md` 决策 - 配置基线:运行环境变量集中见 `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`。T-604 起内容安全配置包含 `MODERATION_ENABLED`、`MODERATION_PROVIDER=keyword`、`MODERATION_FAIL_CLOSED`、`MODERATION_BLOCK_ON_REVIEW`、`MODERATION_CACHE_VERSION_KEY`;生产多 worker 下必须使用共享 cache 承载敏感词版本号。T-612 起生图同步接口止血配置包含 `AI_IMAGE_UPSTREAM_DEADLINE_SECONDS`,只作用于 `generate_image` 上游读取硬截止。T-614/T-616 起异步生图配置包含 `PUBLIC_BASE_URL`、`MEDIA_PUBLIC_BASE_URL`、`IMAGE_TASK_RETENTION_HOURS`、`GENERATED_IMAGE_RETENTION_HOURS`、`IMAGE_TASK_REAPER_INTERVAL_SECONDS`、`IMAGE_TASK_LEASE_SECONDS`、`IMAGE_TASK_MAX_RETRIES`、`IMAGE_TASK_RETRY_BACKOFF_SECONDS`;生产必须运行 `run_image_tasks` worker。 - 当前 blocker:微信正式下单已能返回二维码,但线上微信回调曾出现 `PaymentVerificationError`,仍需单独修复并完成“付款后自动入账”闭环验收;支付宝恢复依赖开放平台把 `43.128.3.240` 加入可信 IP。线上真实标题生成已跑通并验证扣点;图片同步旧接口已由 T-612 加上游硬截止止血,T-614 已提供异步任务化路径,T-615 已补旧同步 / 新异步提交遥测和旧路弃用口径。 @@ -64,7 +64,7 @@ | `init.sh` / `init.ps1` | 已有 | 启动验证入口,已固定系统 Python 3.12 命令,并校验解释器版本 `>=3.12,<3.14` | | `requirements.txt` / `requirements-production.txt` / `pyproject.toml` | 已有 | `requirements.txt` 管本地/基础运行依赖;`requirements-production.txt` 追加 Linux 生产 Gunicorn;`pyproject.toml` 落地 `requires-python`;T-101 新增 `requests`;T-102 使用既有 `cryptography` 做 Fernet 加密;T-501 新增 `django-allauth` | | `config/`(Django 工程) | 已有 | T-001 创建,含 settings / urls / wsgi / asgi | -| `apps/`(users/portal/billing/ai/api/moderation) | 已有 | T-002 创建;`apps/users` 已定义自定义 `User`;T-003 已注册 admin 与 admin smoke test;T-004 已给 `User.email` 加唯一约束;T-101 已新增 `apps/ai/providers`;T-102 已新增 `apps/ai/security.py`、`aliases.py`、`importers.py`、management command 与 `ai.0001_initial` 迁移;T-103 已新增 `apps/ai/audit.py` 与 `ai.0002_aiconfigauditlog` 迁移;T-104/T-105 已新增 `smoke_ai_generation` 录制 title/image smoke 命令;T-201 已在 users 落 `UserWallet` / `ApiKey`,在 billing 落 `PointsLedger` / `CallRecord`;T-202 已在 billing 落 `PricingRule` / `ExchangeRate` 与 `pricing.py`;T-203/T-303/T-304/T-305 已在 `apps/billing/services.py` 落扣点/退点、余额快照、充值入账与充值下单;T-401 已在 `apps/billing/services.py` 落 `adjust_wallet_points()`,在 `apps/users/admin.py` 与 `apps/users/templates/admin/users/userwallet/adjust_points.html` 落钱包手工调点入口;T-608 已在 billing 落 `SignupBonusGrant`、`grant_signup_bonus()`、`signup_bonus` 流水类型、admin 检索和迁移;T-304/T-305 已在 `apps/billing/payment_gateways.py` 落回调验签、mock 下单与 SDK 入口;T-301~T-306 已在 api 落鉴权、生成接口编排、序列化器、图片存储、余额查询、充值回调、充值下单/状态查询、`image_url` SSRF 防护、生成/认证限流与统一 429 错误响应;T-501/T-608 已在 portal 落 allauth 注册/登录/登出路由、模板、adapter 与 dashboard,注册成功自动赠送 100 点;T-502 已在 portal 落 `/apikeys`、API Key 创建表单、列表模板与删除(吊销)动作;T-503/T-608 已在 portal 落个人中心汇总、充值记录和点数记录页;T-504 已在 portal 落 `/recharge` 充值页、充值表单、二维码票据展示和状态轮询;T-505 已在 portal 落本地 vendor static 与记录分页;T-601 已在 `apps/ai/catalog.py` 落公开模型目录,在 api 落 `/api/v1/models`,在 portal 落 `/models` 页面和导航入口;T-604 已在 moderation 落 `SensitiveWord`、keyword matcher、归一化、共享 cache 版本失效与 admin;T-606 已在 portal 落公开首页、`DownloadRelease`、下载版本 admin、`brand.css` 与首页模板;T-607/T-609 已在 api 落 `/api/v1/client/releases/latest` 公开版本检查接口,并在 portal `DownloadRelease` 落 `force_update` 字段与 admin 展示 | +| `apps/`(users/portal/billing/ai/api/moderation) | 已有 | T-002 创建;`apps/users` 已定义自定义 `User`;T-003 已注册 admin 与 admin smoke test;T-004 已给 `User.email` 加唯一约束;T-101 已新增 `apps/ai/providers`;T-102 已新增 `apps/ai/security.py`、`aliases.py`、`importers.py`、management command 与 `ai.0001_initial` 迁移;T-103 已新增 `apps/ai/audit.py` 与 `ai.0002_aiconfigauditlog` 迁移;T-104/T-105 已新增 `smoke_ai_generation` 录制 title/image smoke 命令;T-201 已在 users 落 `UserWallet` / `ApiKey`,在 billing 落 `PointsLedger` / `CallRecord`;T-202 已在 billing 落 `PricingRule` / `ExchangeRate` 与 `pricing.py`;T-203/T-303/T-304/T-305 已在 `apps/billing/services.py` 落扣点/退点、余额快照、充值入账与充值下单;T-401 已在 `apps/billing/services.py` 落 `adjust_wallet_points()`,在 `apps/users/admin.py` 与 `apps/users/templates/admin/users/userwallet/adjust_points.html` 落钱包手工调点入口;T-608 已在 billing 落 `SignupBonusGrant`、`grant_signup_bonus()`、`signup_bonus` 流水类型、admin 检索和迁移;T-304/T-305 已在 `apps/billing/payment_gateways.py` 落回调验签、mock 下单与 SDK 入口;T-301~T-306 已在 api 落鉴权、生成接口编排、序列化器、图片存储、余额查询、充值回调、充值下单/状态查询、`image_url` SSRF 防护、生成/认证限流与统一 429 错误响应;T-501/T-608 已在 portal 落 allauth 注册/登录/登出路由、模板、adapter 与 dashboard,注册成功自动赠送 10 点;T-502 已在 portal 落 `/apikeys`、API Key 创建表单、列表模板与删除(吊销)动作;T-503/T-608 已在 portal 落个人中心汇总、充值记录和点数记录页;T-504 已在 portal 落 `/recharge` 充值页、充值表单、二维码票据展示和状态轮询;T-505 已在 portal 落本地 vendor static 与记录分页;T-601 已在 `apps/ai/catalog.py` 落公开模型目录,在 api 落 `/api/v1/models`,在 portal 落 `/models` 页面和导航入口;T-604 已在 moderation 落 `SensitiveWord`、keyword matcher、归一化、共享 cache 版本失效与 admin;T-606 已在 portal 落公开首页、`DownloadRelease`、下载版本 admin、`brand.css` 与首页模板;T-607/T-609 已在 api 落 `/api/v1/client/releases/latest` 公开版本检查接口,并在 portal `DownloadRelease` 落 `force_update` 字段与 admin 展示 | | `apps/api` 异步生图 | 已有 | T-614 已落 `ImageGenerationTask`、`api.0001_initial`、异步提交/轮询路由、任务 service、worker lease / heartbeat / reaper、`run_image_tasks` 命令和 admin 只读排障;T-616 已落 `api.0002`、`next_attempt_at`、临时性失败自动重试和轮询响应新增重试字段;旧同步生图接口保留 | | `manage.py` | 已有 | T-001 创建 | | `tests/` | 待建 | 随各任务补充 | @@ -73,12 +73,12 @@ 任务状态以 [`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-603 django-admin 中文化(第 4 层·字段级);T-604 中文敏感词本地过滤;T-605 免邮箱验证策略落地;T-606 公开首页 + 客户端下载入口;T-607 桌面端最新版本检查接口;T-608 新用户注册赠送 100 点试用点数;T-609 桌面端版本检查接口增加强制更新标记;T-610 首页导入模板下载入口;T-611 用户端品牌名统一为虾皮圈;T-612 生图同步接口止血(上游硬截止 + 长请求池校准);T-613 抽生成核心 service(计费+审核+上游共享 core);T-614 生图异步任务化接口(提交+轮询,新增不动旧接口);T-615 旧同步生图接口用量遥测 + 弃用口径;T-616 生图失败自动重试 2 次;T-617 桌面端版本检查接口增加文件大小字段;T-618 客户端发布版本后台必填文件校验元数据。 +- 已完成: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-603 django-admin 中文化(第 4 层·字段级);T-604 中文敏感词本地过滤;T-605 免邮箱验证策略落地;T-606 公开首页 + 客户端下载入口;T-607 桌面端最新版本检查接口;T-608 新用户注册赠送试用点数(当前 10 点);T-609 桌面端版本检查接口增加强制更新标记;T-610 首页导入模板下载入口;T-611 用户端品牌名统一为虾皮圈;T-612 生图同步接口止血(上游硬截止 + 长请求池校准);T-613 抽生成核心 service(计费+审核+上游共享 core);T-614 生图异步任务化接口(提交+轮询,新增不动旧接口);T-615 旧同步生图接口用量遥测 + 弃用口径;T-616 生图失败自动重试 2 次;T-617 桌面端版本检查接口增加文件大小字段;T-618 客户端发布版本后台必填文件校验元数据。 - 已完成补充:T-619 多张图片理解并返回文字。 - 正在进行:无。 -- 待开始:仍按真实支付回调到账闭环、客户端发布、生产多图理解模型配置和线上旧同步接口用量观察继续拆任务。 +- 待开始:任务看板当前没有已登记的 TODO;T-621 注册赠点运营后台配置已移入 Backlog。真实支付回调到账闭环、客户端发布、生产多图理解模型配置和线上旧同步接口用量观察仍可继续拆任务。 - 当前 blocker:支付商户真实密钥/证书与生产 SDK 依赖仍待提供;微信回调到账闭环仍需真实支付验收;真实 AI 标题生成已在线上跑通,图片生成慢 / 504 / 客户端超时风险已拆为 T-612~T-616 并完成工程侧处理。 -- 下一个可领取任务:任务看板暂无已登记 TODO;应先依据真实支付回调闭环、客户端发布观察或线上多图图生图验收补充下一项任务。具体范围以 [`06-tasks.md`](06-tasks.md) 为准。 +- 下一个可领取任务:暂无;需先将新的工作项登记为任务后再领取。T-621 保留在 Backlog,具体范围见 [`06-tasks.md`](06-tasks.md)。 ## 当前可运行内容 @@ -140,7 +140,7 @@ python3.12 manage.py run_image_tasks --worker-id cmhub-image-worker-1 --sleep-se - `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/T-613 已落地生成接口与共享核心:旧同步接口仍保持原响应契约;`prepare_generation()` 执行 prompt 审核、图片输入处理、别名解析、Provider 选择和计费计算;`precharge_generation()` 调 billing 预扣;`execute_precharged_generation()` 复用已预扣 `CallRecord` 调上游并成功确认或失败退点;图片结果保存到本地 media 并通过 URL 构建器返回外部 URL。T-303 已落地余额查询接口:`GET /api/v1/balance` 继承外部 API Key 鉴权,读取 billing 余额快照并返回兼容字段 `user` / `points_balance`,以及不含邮箱的 `account.username` / `account.display_name`,测试覆盖余额与流水累加一致。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/T-608 已落地 allauth 注册 / 登录:`ACCOUNT_EMAIL_VERIFICATION="none"`,免邮箱验证、注册即可用,邮箱仍必填且唯一;注册成功经 billing 发放 100 点并写 `signup_bonus` 流水,重复调用或并发触发由 `SignupBonusGrant` 幂等标记兜底,注册限流由 allauth signup rate limit 执行。T-502 已落地 API Key 自助管理:`/apikeys` 登录访问,生成后完整明文只显示一次,列表只显示 prefix,不显示 hash 或历史明文;删除为吊销 `revoked`,吊销后外部 API 返回 403。T-503/T-608 已落地个人中心与记录页:`/dashboard` 展示剩余点数、充值总额、获得点数、净消耗点数和最近记录;`/records/recharge` 展示当前用户充值订单;`/records/usage` 展示当前用户 signup_bonus/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`。T-603 已完成 django-admin 字段级中文化并人工确认字段标签中文。T-604 已落地本地 prompt 敏感词过滤:`MODERATION_ENABLED=false` 默认 no-op,启用 `keyword` 后命中返回 `content_blocked`,并在下载 `image_url`、解析别名、计费、预扣点和上游调用前拦截。T-606 已落地公开首页:`GET /` 匿名 200、不跳登录;下载区读取 Windows 当前 `DownloadRelease`,优先 `external_url`,无当前版本显示「暂未发布」;现有 portal 页面已共用 `brand.css` 品牌 token。T-607/T-609 已落地公开版本检查接口:`GET /api/v1/client/releases/latest` 匿名 200,无需 API Key;支持 `windows`/`macos`/`linux`,返回当前版本 JSON 或 `release:null`,当前版本响应包含 `release.force_update`。真实标题上游生成已在线上跑通并验证扣点;图片生成真实耗时仍需补测。 +当前骨架可运行。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/T-613 已落地生成接口与共享核心:旧同步接口仍保持原响应契约;`prepare_generation()` 执行 prompt 审核、图片输入处理、别名解析、Provider 选择和计费计算;`precharge_generation()` 调 billing 预扣;`execute_precharged_generation()` 复用已预扣 `CallRecord` 调上游并成功确认或失败退点;图片结果保存到本地 media 并通过 URL 构建器返回外部 URL。T-303 已落地余额查询接口:`GET /api/v1/balance` 继承外部 API Key 鉴权,读取 billing 余额快照并返回兼容字段 `user` / `points_balance`,以及不含邮箱的 `account.username` / `account.display_name`,测试覆盖余额与流水累加一致。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/T-608 已落地 allauth 注册 / 登录:`ACCOUNT_EMAIL_VERIFICATION="none"`,免邮箱验证、注册即可用,邮箱仍必填且唯一;注册成功经 billing 发放 10 点并写 `signup_bonus` 流水,重复调用或并发触发由 `SignupBonusGrant` 幂等标记兜底,注册限流由 allauth signup rate limit 执行。T-502 已落地 API Key 自助管理:`/apikeys` 登录访问,生成后完整明文只显示一次,列表只显示 prefix,不显示 hash 或历史明文;删除为吊销 `revoked`,吊销后外部 API 返回 403。T-503/T-608 已落地个人中心与记录页:`/dashboard` 展示剩余点数、充值总额、获得点数、净消耗点数和最近记录;`/records/recharge` 展示当前用户充值订单;`/records/usage` 展示当前用户 signup_bonus/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`。T-603 已完成 django-admin 字段级中文化并人工确认字段标签中文。T-604 已落地本地 prompt 敏感词过滤:`MODERATION_ENABLED=false` 默认 no-op,启用 `keyword` 后命中返回 `content_blocked`,并在下载 `image_url`、解析别名、计费、预扣点和上游调用前拦截。T-606 已落地公开首页:`GET /` 匿名 200、不跳登录;下载区读取 Windows 当前 `DownloadRelease`,优先 `external_url`,无当前版本显示「暂未发布」;现有 portal 页面已共用 `brand.css` 品牌 token。T-607/T-609 已落地公开版本检查接口:`GET /api/v1/client/releases/latest` 匿名 200,无需 API Key;支持 `windows`/`macos`/`linux`,返回当前版本 JSON 或 `release:null`,当前版本响应包含 `release.force_update`。真实标题上游生成已在线上跑通并验证扣点;图片生成真实耗时仍需补测。 T-614 已落地异步生图任务化:提交接口预扣后返回 `task_id`,轮询接口返回 queued/running/succeeded/failed,worker 和 reaper 复用同一套成功确认 / 失败退点路径,旧同步接口继续兼容。当前 worker 会复跑审核 / 别名解析 / 定价,但不重复扣点;`image_url` 输入会在 submit 阶段下载,桌面端主链路应优先用 `image_base64`。T-615 已落地旧同步 / 新异步提交遥测:两个入口都会写 `generation_route_usage` 结构化日志,便于按客户端版本、API Key 和 route_type 观察迁移进度;旧同步接口下线前必须继续保持响应兼容,并单独立下线任务。 T-619 已落地同步多图理解:调用方提交有序 `images` 列表,服务按 `vision + alias` 默认价固定扣点,成功返回文字,失败幂等退款;输入图片和 provider raw 不写入调用记录。代码部署并迁移后,需由运营配置视觉模型、默认别名与默认价格,才会成为可调用能力。 diff --git a/docs/deployment.md b/docs/deployment.md index 0bb0538..cc4b85e 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -148,7 +148,7 @@ python3.12 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key 注意: - `AI_KEY_ENCRYPTION_KEY` 生产不可随意更换;更换会导致已加密的 `AiModel.api_key_encrypted` 无法解密。 -- 当前注册策略为免邮箱验证,注册登录不依赖邮件服务;T-608 后注册成功会赠送 100 点,生产必须保留或收紧 `ACCOUNT_SIGNUP_RATE_LIMIT`,并使用共享 Django cache 承载限流计数;若后续启用密码找回、通知或恢复邮箱验证,再把 `DJANGO_EMAIL_BACKEND` 改为真实 SMTP / 邮件服务并配置 `DJANGO_DEFAULT_FROM_EMAIL`。 +- 当前注册策略为免邮箱验证,注册登录不依赖邮件服务;新注册用户成功后会赠送 10 点,生产必须保留或收紧 `ACCOUNT_SIGNUP_RATE_LIMIT`,并使用共享 Django cache 承载限流计数;若后续启用密码找回、通知或恢复邮箱验证,再把 `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 误锁域名。 - T-614 异步生图 worker 没有 request 对象,生产必须配置 `MEDIA_PUBLIC_BASE_URL` 或 `PUBLIC_BASE_URL` 为公开 HTTPS 域名,否则异步轮询成功时可能返回相对 `/media/...` URL。 diff --git a/docs/mvp-acceptance.md b/docs/mvp-acceptance.md index b897c08..5e7a83c 100644 --- a/docs/mvp-acceptance.md +++ b/docs/mvp-acceptance.md @@ -8,7 +8,7 @@ | P0 验收项 | 结论 | 主要证据 | | --- | --- | --- | -| 用户注册 / 登录 | PASS | T-402 历史验收口径为 `apps.portal` 注册创建 0 点钱包且不写赠点流水;T-605 后注册为免邮箱验证、注册后可直接登录,邮箱仍必填且唯一。T-608 已按新需求改为注册赠送 100 点并写 `signup_bonus` 流水,且通过 `SignupBonusGrant(user UNIQUE)` 防重复发放 | +| 用户注册 / 登录 | PASS | T-402 历史验收口径为 `apps.portal` 注册创建 0 点钱包且不写赠点流水;T-605 后注册为免邮箱验证、注册后可直接登录,邮箱仍必填且唯一。T-608 已按新需求改为注册赠点并写 `signup_bonus` 流水,且通过 `SignupBonusGrant(user UNIQUE)` 防重复发放;当前额度为 10 点。 | | API Key 自助管理 | PASS | `apps.portal`:Key 明文只显示一次、列表仅显示 prefix、删除为吊销、不可删除他人 Key;`apps.api`:缺失/无效 Key 为 401,吊销 Key 为 403 | | 扫码充值 | PASS | `apps.portal` / `apps.api`:创建 pending 订单不直接加点、不写流水;二维码票据展示;状态轮询只允许本人订单;主动查单可补入账 | | 个人中心 / 记录 | PASS | `apps.portal`:余额、充值总额、获得点数、注册赠点/消费/退款记录与实际订单/流水一致;充值记录和点数记录分页且仅见本人 | @@ -38,7 +38,7 @@ ## 已知限制 -- 一次单条 `py -3.12 manage.py test --noinput --keepdb --verbosity 2` 发现 119 tests,已运行 98 tests 后远程 MySQL 在 `apps.portal.tests.PortalAccountFlowTests.setUpClass` 阶段连接重置;已通过用例无断言失败。随后按 `portal` / `ai` / `api` / `billing+users` 四组拆分,合计同一 119 条测试全部通过。 +- 一次单条 `py -3.12 manage.py test --noinput --keepdb --verbosity 2` 发现 119 tests,已运行 98 tests 后远程 MySQL 在 `apps.portal.tests.PortalAccountFlow-ests.setUpClass` 阶段连接重置;已通过用例无断言失败。随后按 `portal` / `ai` / `api` / `billing+users` 四组拆分,合计同一 119 条测试全部通过。 - 测试阶段仍有 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 d7d11ce..b35c9ef 100644 --- a/docs/project-brief.md +++ b/docs/project-brief.md @@ -60,7 +60,7 @@ **第一版做**: -- 用户端:自助注册登录、注册成功赠送 100 点试用点数、扫码充值、查看点数/充值/消费记录、自助生成与删除 API Key。 +- 用户端:自助注册登录、注册成功赠送 10 点试用点数、扫码充值、查看点数/充值/消费记录、自助生成与删除 API Key。 - 生成标题、生成图片接口;图片保留旧同步接口,并新增异步提交 / 轮询接口给新版客户端。 - 点数计费、扣点、余额查询。 - 充值到账(对接已有支付系统)。 @@ -69,7 +69,7 @@ **第一版先不做**(留待后续迭代): -- 用量统计报表、自动退款对账、复杂活动赠点 / 邀请奖励等(自助注册/扫码充值/API Key 管理、注册送 100 点与生图异步提交 / 轮询已纳入当前口径)。 +- 用量统计报表、自动退款对账、复杂活动赠点 / 邀请奖励等(自助注册/扫码充值/API Key 管理、注册送 10 点与生图异步提交 / 轮询已纳入当前口径)。 > 取舍原则:先把「充值 → 调用 → 计费 → 记录」最小闭环做稳,再扩展。 @@ -108,7 +108,7 @@ - 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`。 -- Phase 6 已完成 T-601 可用别名发现、T-602 后台表名/分组中文化、T-603 字段级中文化、T-604 中文敏感词本地过滤、T-605 免邮箱验证策略落地、T-606 公开首页 + 客户端下载入口、T-607 桌面端最新版本检查接口、T-608 新用户注册赠送 100 点试用点数、T-609 桌面端版本强制更新标记、T-610 首页导入模板下载入口、T-611 用户端品牌名统一、T-612~T-615 生图异步化与旧同步接口遥测;线上真实标题生成已恢复并验证扣点。 +- Phase 6 已完成 T-601 可用别名发现、T-602 后台表名/分组中文化、T-603 字段级中文化、T-604 中文敏感词本地过滤、T-605 免邮箱验证策略落地、T-606 公开首页 + 客户端下载入口、T-607 桌面端最新版本检查接口、T-608 新用户注册赠送试用点数(当前 10 点)、T-609 桌面端版本强制更新标记、T-610 首页导入模板下载入口、T-611 用户端品牌名统一、T-612~T-615 生图异步化与旧同步接口遥测;线上真实标题生成已恢复并验证扣点。 - 下一步可继续补跑真实支付回调到账闭环、配置并发布客户端下载包,并在生产日志中观察旧同步生图接口用量与弃用条件。 --- diff --git a/docs/project-onepager.md b/docs/project-onepager.md index 3ee9249..596eee2 100644 --- a/docs/project-onepager.md +++ b/docs/project-onepager.md @@ -29,8 +29,8 @@ ## 第一版范围 -做:自助用户端(注册注册送 100 点/扫码充值/API Key 管理/记录)、两个生成接口、点数计费、充值到账、调用记录、运营后台。 -先不做:报表、复杂活动赠点 / 邀请奖励、自动退款对账(后续迭代;自助注册/充值/API Key、注册送 100 点和生图异步提交 / 轮询已在当前版本)。 +做:自助用户端(注册注册送 10 点/扫码充值/API Key 管理/记录)、两个生成接口、点数计费、充值到账、调用记录、运营后台。 +先不做:报表、复杂活动赠点 / 邀请奖励、自动退款对账(后续迭代;自助注册/充值/API Key、注册送 10 点和生图异步提交 / 轮询已在当前版本)。 ## 需要拍板 @@ -40,7 +40,7 @@ ## 进度 -M1 骨架已完成;M2 已通过录制生成 smoke 验证 AI 调用链路;M3 计费、对外 API、充值闭环与上线前安全加固已完成到 T-306;M4 用户端已完成注册 / 登录、API Key 管理、个人中心 / 记录页、充值页和审核优化;M5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档;M7 注册试用额度已完成,注册后经账本发放 100 点并留流水;M11 生图异步任务化已完成到 T-615,旧同步接口继续兼容并写结构化用量日志。下一步是在 VPS 上按部署文档接真实支付回调闭环、发布客户端,并观察旧同步生图用量。里程碑:M1 骨架可跑 · M2 跑通生成 · M3 计费充值闭环 · M4 用户端可用 · M5 验收上线 · M7 试用额度安全落地 · M11 生图异步任务化。 +M1 骨架已完成;M2 已通过录制生成 smoke 验证 AI 调用链路;M3 计费、对外 API、充值闭环与上线前安全加固已完成到 T-306;M4 用户端已完成注册 / 登录、API Key 管理、个人中心 / 记录页、充值页和审核优化;M5 已完成 T-401 运营后台完善、T-402 MVP 完整验收与 T-403 部署 / 运行文档;M7 注册试用额度已完成,注册后经账本发放 10 点并留流水;M11 生图异步任务化已完成到 T-615,旧同步接口继续兼容并写结构化用量日志。下一步是在 VPS 上按部署文档接真实支付回调闭环、发布客户端,并观察旧同步生图用量。里程碑:M1 骨架可跑 · M2 跑通生成 · M3 计费充值闭环 · M4 用户端可用 · M5 验收上线 · M7 试用额度安全落地 · M11 生图异步任务化。 --- *详见 `project-brief.md`(完整介绍)。* diff --git a/docs/routes.md b/docs/routes.md index bf071b4..d30e5bf 100644 --- a/docs/routes.md +++ b/docs/routes.md @@ -8,7 +8,7 @@ | 路由 | 方法 | 职责 | 鉴权 | | --- | --- | --- | --- | | `/` | GET | 公开首页:项目介绍、四步上手、客户端下载入口、导入模板下载入口、API/控制台入口 | 公开 | -| `/signup` `/login` `/logout` | GET/POST | 自助注册(**免邮箱验证、注册即可用**;注册成功一次性赠送 100 点试用点数)/登录/登出(Django auth/allauth) | 公开 | +| `/signup` `/login` `/logout` | GET/POST | 自助注册(**免邮箱验证、注册即可用**;注册成功一次性赠送 10 点试用点数)/登录/登出(Django auth/allauth) | 公开 | | `/dashboard` | GET | 个人中心:剩余点数、充值总额、快捷入口 | session | | `/recharge` | GET/POST | 发起充值:选金额→展示支付二维码→轮询到账 | session | | `/records/recharge` | GET | 充值记录 | session | @@ -16,7 +16,7 @@ | `/apikeys` | GET/POST | API Key 管理:列表 / 生成 / 删除(删除即吊销,明文只显示一次) | session | | `/models` | GET | 可用模型:只读展示可调用能力别名、能力、是否需要原图和点数单价 | session | -T-501/T-608 已落地 `/signup`、`/login`、`/logout` 与 `/dashboard`:注册成功后经计费层一次性发放 100 点试用点数并写 `signup_bonus` 流水,注册限流由 allauth signup rate limit 执行。T-502 已落地 `/apikeys`:登录用户只能管理自己的 Key,生成后明文只显示一次,列表只显示 prefix,删除为吊销 `revoked`。T-503/T-505/T-608 已扩展 `/dashboard` 为个人中心汇总,并落地 `/records/recharge` 与 `/records/usage`:充值总额按已支付订单统计,注册赠点 / 入账 / 消费 / 退款点数按 `PointsLedger` 统计,记录页只查询当前登录用户数据并分页展示。T-504/T-505 已落地 `/recharge`:登录用户可选择金额和支付方式创建 pending 充值订单,页面用本地 static 自托管 qrcode.js 展示二维码票据并轮询 `/api/v1/recharge/status`,到账后刷新余额。 +T-501/T-608 已落地 `/signup`、`/login`、`/logout` 与 `/dashboard`:注册成功后经计费层一次性发放 10 点试用点数并写 `signup_bonus` 流水,注册限流由 allauth signup rate limit 执行。T-502 已落地 `/apikeys`:登录用户只能管理自己的 Key,生成后明文只显示一次,列表只显示 prefix,删除为吊销 `revoked`。T-503/T-505/T-608 已扩展 `/dashboard` 为个人中心汇总,并落地 `/records/recharge` 与 `/records/usage`:充值总额按已支付订单统计,注册赠点 / 入账 / 消费 / 退款点数按 `PointsLedger` 统计,记录页只查询当前登录用户数据并分页展示。T-504/T-505 已落地 `/recharge`:登录用户可选择金额和支付方式创建 pending 充值订单,页面用本地 static 自托管 qrcode.js 展示二维码票据并轮询 `/api/v1/recharge/status`,到账后刷新余额。 T-601 已落地 `/models`:登录用户可查看当前公开可调用别名、能力、是否需要原图和点数单价;页面不展示底层 SKU、模型 URL、provider key、`api_key_encrypted` 或 `extra_body`。 T-606 已落地 `/` 公开首页:匿名访问返回 200,不再重定向到 `/dashboard`;匿名用户看到注册 / 登录 / 下载入口,登录用户看到「进入控制台」。首页下载区读取 `DownloadRelease(platform=windows, is_current=True)`,优先使用 `external_url`,否则使用后台上传文件的 `file.url`;无当前版本时显示「暂未发布」。T-610 已在同一下载区“下载客户端”右侧增加“下载导入模板”,模板由后台配置当前版本,匿名可下载;模板本地文件 URL 会转成当前站点绝对 URL,页面不暴露本地文件路径。 diff --git a/progress.md b/progress.md index cfabeb8..ca074bd 100644 --- a/progress.md +++ b/progress.md @@ -2001,3 +2001,28 @@ - 安全回归:完整 `GenerateApiTests` 首次运行 53 条时发现旧 `image_url` 会因新多图单图限制而绕过 `IMAGE_URL_MAX_BYTES`。已修复为 URL 下载取既有 URL 上限与请求上下文单图上限中的较小值,恢复旧接口保护语义。 - 文档:同步更新 `README.md`、`00-ai-start-here.md`、`02-requirements.md`、`04-architecture.md`、`api.md`、`routes.md`、`env.md`、`deployment.md`、`current-state.md` 与任务看板,明确多图请求格式、主图 / 参考图语义、异步存储和 Nginx 请求体配置。 - 验证:`C:/Python312/python.exe -m compileall -q apps config`、`manage.py check`、`manage.py makemigrations --check --dry-run` 通过;迁移 `api.0003_image_generation_task_input` 已应用到当前开发库。Provider 测试 13 条通过;序列化器多图、空数组与混用边界通过;目标 API 6 条通过(同步多图、URL+base64 混合、混用拒绝、超限拒绝、异步存储 / worker 恢复、旧 URL 上限)。完整 53 条 `GenerateApiTests` 的修复后重跑受远程 MySQL 测试库耗时影响超过 5 分钟未完成,未出现新的失败输出;此前完整运行唯一失败已修复并由目标回归用例确认。中转站两图 `images/edits` 重复 multipart `image` 直连验收此前返回 HTTP 200 和有效图片,作为真实上游多文件传输证据。 + +## 2026-07-18 文档登记:T-621 注册赠点运营后台配置 + +- 背景:当前新用户赠点由 `grant_signup_bonus(points=100)` 的代码默认值决定,既不读取 `.env`,也不能在 django-admin 调整。运营需要在不重启服务的前提下管理未来新注册用户的试用额度。 +- 决策:新增数据库单例 `SignupBonusPolicy`,默认启用并赠送 100 点;admin 只能编辑,不允许新增或删除。配置改动须写独立只读审计记录,避免将 Django `LogEntry` 作为唯一业务审计。 +- 账务口径:赠点服务在同一事务内读取策略、锁钱包并创建既有 `SignupBonusGrant(user UNIQUE)`。策略停用时仍写 `points_granted=0` 的决定记录、不写零额流水,防止注册回调重试或后续重新开启策略对同一用户补发;历史用户的已发点数、流水和余额不回算。 +- 任务文档:`docs/06-tasks.md` 新增 T-621 和 M17;`docs/current-state.md` 更新为下一可领取任务。未改代码、数据库或线上配置。 + +## 2026-07-18 评审修订:T-621 卡片按设计评审优化 + +- 背景:对 T-621 卡片做实现前评审,发现并发锁语义、约束设计和若干实现细节可优化,已把结论落回 `docs/06-tasks.md` 的 T-621 描述。未改代码 / 数据库。 +- 并发锁(重要):原措辞「配置修改和注册赠点行锁序列化」会导致对策略单例行 `pk=1` 加排他锁、把所有注册全局串行化。修订为:策略只做只读快照(不 `select_for_update`),单用户幂等由 `SignupBonusGrant(user UNIQUE)` + 钱包行锁保证,不同用户注册不相互阻塞。 +- 约束简化:`points` 由「启用 1..10000 / 停用 0..10000」两套条件约束,简化为 DB 统一 `0 <= points <= 10000`,「启用需 >= 1」放表单 / `clean()` 层校验;停用时 `points` 不参与发放。 +- 实现细节明确:`grant_signup_bonus()` 移除 / 忽略 `points` 入参(一律以策略为准,避免双真相源);停用 `points=0` 路径必须绕过 `_validate_positive_points`(其拒绝 `<= 0`,否则连带回滚用户创建)。 +- 可读性:`SignupBonusGrant` 现含赠 0 决定记录,`verbose_name` / 列表展示澄清为「注册赠点处理记录(含赠 0)」;`points` 上限 `10000` 在 `help_text` 注明口径来源。 +- 测试补充:停用 `points=0` 不被正数校验误杀;不同用户并发注册不因策略读取相互串行 / 阻塞。 + +## 2026-07-18 决策:T-621 移入 Backlog,注册赠点调整为 10 点 + +- 决策:T-621「注册赠点运营后台配置」不再作为可领取任务,移入 `docs/06-tasks.md` 的 Backlog;不新增 `SignupBonusPolicy`、审计表或 admin 配置,当前额度继续由代码默认值控制。 +- 口径:后续新注册用户一次性赠送 **10 点**;已注册用户的余额、注册赠点记录与流水不回算、不补发、不扣回。 +- 实现:`grant_signup_bonus()` 默认值、`SignupBonusGrant.points_granted` 默认值、用户端注册/首页文案和相关 billing / portal 测试同步改为 10;新增 `billing.0009_signup_bonus_default_ten`,仅更新后续插入记录的数据库默认值。 +- 安全:仍复用既有 `SignupBonusGrant(user UNIQUE)` 数据库级幂等标记和 billing 事务写账;此次只调整额度,不改变注册接入、钱包锁、流水或 API 契约。 +- 本地验证:`C:/Python312/python.exe manage.py migrate --noinput` 已应用 `billing.0009_signup_bonus_default_ten`;`manage.py check` 0 issues;`makemigrations --check --dry-run` 无待生成迁移;`compileall` 通过。定向 MySQL 测试 5 条通过:服务默认发放 / 已有钱包累加 / 并发仅发一次 / 注册 adapter 写余额与流水 / 首页匿名文案。测试仅出现既有 allauth 条件唯一约束在 MySQL 上不创建的 `models.W036` 警告。 +- 线上部署结果将在本次提交后补充。