Skip to content

feat: 订阅套餐支持限定额度可用分组(quota_usable_groups) - #6133

Closed
zhoushun98 wants to merge 2 commits into
QuantumNous:mainfrom
zhoushun98:feat/subscription-quota-usable-groups
Closed

feat: 订阅套餐支持限定额度可用分组(quota_usable_groups)#6133
zhoushun98 wants to merge 2 commits into
QuantumNous:mainfrom
zhoushun98:feat/subscription-quota-usable-groups

Conversation

@zhoushun98

@zhoushun98 zhoushun98 commented Jul 11, 2026

Copy link
Copy Markdown

📝 变更描述 / Description

问题:订阅计费选择资金来源时只看 billing_preference 与是否存在活跃订阅,PreConsumeUserSubscription 挑选订阅只按 end_time 排序,全程不校验请求使用的分组。导致订阅额度可以被任意分组消耗——典型套利场景:套餐绑定 1x 分组,用户创建 0.05x 低倍率分组的令牌烧订阅额度,等效额度被放大 20 倍(即 #5200 的根因)。

方案:给套餐增加独立字段 quota_usable_groups(varchar(255),逗号分隔,留空 = 不限制,完全向后兼容),不绑死在 upgrade_group 上(不升级分组的套餐同样需要限制):

  • PreConsumeUserSubscription 增加 group 参数(来自 relayInfo.UsingGroup,即请求实际使用的分组),候选循环中跳过额度不适用于该分组的订阅;多订阅共存时只消耗匹配的订阅
  • NewBillingSession 默认策略(subscription_first)下,若无额度可用于当前分组的活跃订阅,直接走钱包余额。分组不匹配不属于额度耗尽,因此不受 allow_wallet_overflow 限制;subscription_only 则按订阅额度不足报错
  • 管理端创建/更新套餐接口校验分组存在于 GroupRatio 并持久化(更新走 updateMap,避免旧前端保存时抹掉该字段)
  • default / classic 两套前端套餐表单增加「额度可用分组」输入框及 i18n 词条

按「请求分组」而非「模型名」校验的原因:计费倍率本身按分组计算,同一模型可存在于多个分组,按分组限制与计费闭环一致。

🚀 变更类型 / Type of change

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 已搜索现有 Issues 与 PRs,Bug: 订阅计费未校验模型分组 — 跨分组模型也能扣订阅额度 #5200 描述的是同一根因,本 PR 为其实现。
  • Bug fix 说明: 不适用(标记为新功能)。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试,另在生产环境(v1.0.0-rc.21 + 本补丁)实测验证。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

单元测试(新增 model/subscription_group_limit_test.goservice/billing_session_group_test.go,覆盖:分组匹配/不匹配、多订阅只消耗匹配项、无匹配订阅时回退钱包):

ok  github.com/QuantumNous/new-api/model       7.557s
ok  github.com/QuantumNous/new-api/service     1.848s
ok  github.com/QuantumNous/new-api/controller  2.298s

生产实测(套餐 quota_usable_groups='codex团队版',同一用户同一模型各请求一次,logs.other 摘录):

group=codex团队版  →  "billing_source":"subscription", "group_ratio":1     (订阅 amount_used 增加,余额不变)
group=codex 特惠   →  "billing_source":"wallet",       "group_ratio":0.05  (余额减少,订阅 amount_used 不变)

留空 quota_usable_groups 的既有套餐行为与现状完全一致;新列由 AutoMigrate 自动创建。

Summary by CodeRabbit

  • New Features
    • Added subscription plan quota restrictions by selectable groups.
    • Added quota_usable_groups support across admin and operator subscription plan forms (multi-select; empty means unrestricted).
    • Updated billing session funding selection to use subscription quota only when the request group is permitted; otherwise it falls back to wallet balance.
  • Bug Fixes
    • Subscription eligibility and quota pre-consumption now correctly respect the request’s group.
  • Tests
    • Added coverage for group-restricted quota behavior and funding source selection.
  • Documentation
    • Added English and Chinese UI/help text for the new restriction setting.

套餐新增 quota_usable_groups 字段(逗号分隔,留空不限制):
- 预扣费时跳过不适用于当前请求分组的订阅,仅消耗匹配的订阅额度
- subscription_first 策略下,请求分组无可用订阅时直接改走钱包余额
  (分组不匹配不属于额度耗尽,不受 allow_wallet_overflow 限制)
- 管理端创建/更新套餐接口校验并持久化该字段
- default / classic 两套前端套餐表单支持配置该字段
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8670cfa0-ca1c-4f72-9bae-766fdacc9122

📥 Commits

Reviewing files that changed from the base of the PR and between 3009067 and 004aad2.

📒 Files selected for processing (6)
  • web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx
  • web/classic/src/i18n/locales/en.json
  • web/default/src/features/subscriptions/components/subscriptions-mutate-drawer.tsx
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/static-keys.ts
✅ Files skipped from review due to trivial changes (3)
  • web/default/src/i18n/static-keys.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/classic/src/i18n/locales/en.json
  • web/default/src/features/subscriptions/components/subscriptions-mutate-drawer.tsx

Walkthrough

Subscription plans gain optional quota-usable group restrictions. Admin APIs validate and persist groups, billing filters subscription funding by request group, tests cover the behavior, and both subscription administration UIs expose localized form fields.

Changes

Subscription quota enforcement

Layer / File(s) Summary
Plan model and group-aware selection
model/subscription.go, model/subscription_group_limit_test.go, model/task_cas_test.go
Plans parse and evaluate usable groups; subscription availability and pre-consumption skip plans that do not allow the requested group.
Admin plan validation and persistence
controller/subscription.go
Create and update handlers validate groups against configured ratios and persist the normalized group list.
Billing funding integration
service/billing_session.go, service/funding_source.go, service/*group_test.go, service/task_billing_test.go
Billing passes the request group into subscription funding and selects wallet or subscription funding according to group eligibility.
Subscription plan administration UI
web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx, web/default/src/features/subscriptions/*, web/*/i18n/*
Both subscription plan forms accept, serialize, validate, and describe quota usable groups.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdminUI
  participant SubscriptionController
  participant SubscriptionPlan
  participant BillingSession
  participant Wallet
  AdminUI->>SubscriptionController: submit quota usable groups
  SubscriptionController->>SubscriptionPlan: validate and persist groups
  BillingSession->>SubscriptionPlan: check request group eligibility
  alt eligible subscription
    BillingSession->>SubscriptionPlan: pre-consume subscription quota
  else ineligible subscription
    BillingSession->>Wallet: use wallet funding
  end
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: Calcium-Ion, seefs001

Poem

I’m a rabbit with groups in my hat,
Quota hops where it’s welcome at.
Wallets wait by the garden gate,
Plans now check before they allocate.
“Vip!” I cheer, “default!” I sing—
Every little quota knows its wing.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main feature change: adding quota_usable_groups support for subscription plans.
Linked Issues check ✅ Passed The changes address issue #5200 by validating request groups during pre-consumption and preventing cross-group subscription quota usage.
Out of Scope Changes check ✅ Passed The added tests, migrations, admin updates, and frontend form changes all support the subscription group-restriction feature.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx (1)

328-346: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a multi-select instead of freeform text for consistency and typo-safety.

groupOptions is already fetched here and used to populate upgrade_group as a Form.Select. quota_usable_groups instead uses a plain Form.Input for comma-separated names, so a typo only surfaces as a server-side error after submit ("额度可用分组不存在: ..." per controller/subscription.go). Sourcing this field from the same groupOptions (e.g. a multi-select) would prevent invalid group names up front and align the UX with the adjacent field.

Also applies to: 348-358

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx`
around lines 328 - 346, The upgrade_group and quota_usable_groups fields should
use a multi-select sourced from the existing groupOptions instead of freeform
text or single selection. Update the Form.Select configuration and the adjacent
quota_usable_groups field to support selecting multiple valid groups, preserving
the existing loading, clear, labels, and translation behavior while preventing
arbitrary group names.
web/default/src/features/subscriptions/components/subscriptions-mutate-drawer.tsx (1)

478-501: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a multi-select bound to groupOptions instead of free-text CSV.

upgrade_group/downgrade_group use a Select populated from groupOptions to prevent typos; quota_usable_groups accepts arbitrary comma-separated text with no client-side validation against known groups. Invalid group names will only be caught after a round-trip to the admin API (额度可用分组不存在: ...), which is a worse UX than the existing group pickers in this same form.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/subscriptions/components/subscriptions-mutate-drawer.tsx`
around lines 478 - 501, Replace the free-text quota_usable_groups Input with the
form’s existing multi-select pattern, binding selected values to
quota_usable_groups and populating options from groupOptions. Preserve empty
selection as no restriction, and reuse the established group picker behavior
used by upgrade_group and downgrade_group so users can only choose valid groups.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@web/default/src/features/subscriptions/components/subscriptions-mutate-drawer.tsx`:
- Around line 492-496: The FormDescription in the subscription quota
configuration must reflect billing preference behavior: under
subscription_first, unmatched groups fall back to wallet balance; under
subscription_only, they are treated as insufficient subscription quota. Update
the translation text around the subscription quota field to communicate this
distinction accurately.

---

Nitpick comments:
In
`@web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx`:
- Around line 328-346: The upgrade_group and quota_usable_groups fields should
use a multi-select sourced from the existing groupOptions instead of freeform
text or single selection. Update the Form.Select configuration and the adjacent
quota_usable_groups field to support selecting multiple valid groups, preserving
the existing loading, clear, labels, and translation behavior while preventing
arbitrary group names.

In
`@web/default/src/features/subscriptions/components/subscriptions-mutate-drawer.tsx`:
- Around line 478-501: Replace the free-text quota_usable_groups Input with the
form’s existing multi-select pattern, binding selected values to
quota_usable_groups and populating options from groupOptions. Preserve empty
selection as no restriction, and reuse the established group picker behavior
used by upgrade_group and downgrade_group so users can only choose valid groups.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48abc81c-753e-469e-8374-15f444d08422

📥 Commits

Reviewing files that changed from the base of the PR and between 7c28993 and 3009067.

📒 Files selected for processing (16)
  • controller/subscription.go
  • model/subscription.go
  • model/subscription_group_limit_test.go
  • model/task_cas_test.go
  • service/billing_session.go
  • service/billing_session_group_test.go
  • service/funding_source.go
  • service/task_billing_test.go
  • web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx
  • web/classic/src/i18n/locales/en.json
  • web/default/src/features/subscriptions/components/subscriptions-mutate-drawer.tsx
  • web/default/src/features/subscriptions/lib/plan-form.ts
  • web/default/src/features/subscriptions/types.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/static-keys.ts

- 额度可用分组改为基于分组列表的多选下拉(default / classic),无效分组名在前端即可拦截,与相邻的升级/降级分组字段交互一致
- 修正字段描述:分组不匹配回退钱包仅适用于订阅优先策略,计费偏好为仅订阅时将按订阅额度不足处理
@zhoushun98

Copy link
Copy Markdown
Author

撤回此 PR:该功能改为在自建部署侧自行维护,不再推进上游合并。感谢 review,打扰了。

@zhoushun98 zhoushun98 closed this Jul 13, 2026
@zhoushun98
zhoushun98 deleted the feat/subscription-quota-usable-groups branch July 13, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: 订阅计费未校验模型分组 — 跨分组模型也能扣订阅额度

1 participant