如何设计额度和权限表

额度和权限是 AI 产品商业化和安全运营的两大基础。额度控制用户「能用多少」,权限决定用户「能做什么」。二者共同构成产品的商业化边界与安全屏障——额度表错了,用户可能无限消耗 API 调用导致成本失控;权限表错了,普通用户可能访问到管理员功能,造成数据泄露。

本文从数据库表结构层面,系统拆解额度表设计、权限表设计、RBAC 模型,以及二者之间的关联方式,帮助你在 AI 产品出海的过程中建立可靠的用量控制与访问控制体系。

一、额度表设计

1.1 为什么需要独立的额度表

在 AI 产品中,每一次生成、每一次对话、每一次图像渲染都有真实的计算成本。如果不做额度管理,产品只能依赖简单的请求频率限制(Rate Limit),无法实现按量计费、分层套餐、免费试用等商业化策略。

额度表的核心职责是回答三个问题:

  1. 这个用户还有多少可用额度?
  2. 额度从哪里来、怎么刷新?
  3. 额度用尽时如何处理?

1.2 额度表字段设计

一个典型的额度表 user_quotas 需要包含以下字段:

字段类型说明
idUUID / BIGSERIAL主键
user_idUUID关联用户表
quota_typeVARCHAR(50)额度类型,如 image_generationtext_completionapi_call
total_amountBIGINT当前周期内总额度
used_amountBIGINT已使用额度
remaining_amountBIGINT剩余额度(可用计算列或缓存字段)
period_startTIMESTAMPTZ当前周期开始时间
period_endTIMESTAMPTZ当前周期结束时间
refresh_policyVARCHAR(20)刷新策略:dailymonthlyyearlyone_time
statusVARCHAR(20)状态:activeexhaustedsuspended
created_atTIMESTAMPTZ创建时间
updated_atTIMESTAMPTZ更新时间

几个设计要点:

  • remaining_amount 是否冗余:可以设为计算列(total_amount - used_amount),但在高并发场景下,冗余存储并配合数据库事务能保证原子性,避免重复计算带来的性能开销。
  • quota_type 用字符串而非枚举:AI 产品的额度类型会频繁增加(新的模型、新的功能),字符串更容易扩展。如果追求强类型约束,可以使用数据库枚举并配合 migration 更新。
  • 周期字段period_startperiod_end 让额度过期和刷新逻辑变得明确,避免依赖模糊的「上次刷新时间」推断。

1.3 额度类型与来源

AI 产品的额度通常分为两类:

额度类型说明典型场景
免费额度(Free Quota)注册即赠,用于产品试用新用户赠送 50 次图片生成
付费额度(Paid Quota / Credits)用户购买获得,按量消耗购买 1000 Credits 用于 API 调用

在表结构上,有两种处理方式:

方案 A:单表多行——同一个 user_quotas 表,通过 quota_type 区分免费和付费额度。每行代表一种额度来源。优点是查询简单,缺点是无法表达优先级。

方案 B:增加 source 字段——在表中增加 source 字段(freepurchasedgifted),并增加 priority 字段决定扣减顺序。扣减时按优先级从高到低消耗,优先使用免费额度。

推荐方案 B,因为它更贴近真实的业务逻辑:大多数产品都会优先消耗免费额度,再用付费额度。

1.4 额度刷新策略

额度刷新是设计中容易忽视的环节。不同产品有不同的刷新节奏:

刷新策略适用场景实现方式
每日刷新(Daily)高频 API 调用限制定时任务每日 00:00 重置 used_amount
每月刷新(Monthly)SaaS 订阅套餐在订阅周期开始时重置额度
一次性(One-time)购买的 Credits 包不刷新,用完即止
按周期滚动(Rolling)企业级合同未使用额度按比例结转至下一周期

实现刷新的关键是幂等性:刷新操作必须确保同一周期内不会重复执行。常见做法是在 user_quotas 表上记录 last_refreshed_period(如 2026-07),刷新前先检查该字段,如果已经等于当前周期则跳过。

1.5 额度扣减与超限处理

额度扣减必须在事务中完成,核心流程是:

  1. 查询用户当前可用额度(SELECT ... FOR UPDATE 行级锁)。
  2. 判断剩余额度是否足够本次消费。
  3. 如果足够,UPDATE used_amount = used_amount + cost 并返回成功。
  4. 如果不足,根据产品策略执行超限处理。

超限处理策略包括:

  • 硬限制:直接拒绝请求,返回 402 Payment Required429 Too Many Requests
  • 软限制 + 通知:允许一定程度的超额使用,同时发送邮件或站内通知提醒用户充值。
  • 自动升级:当免费额度耗尽时,自动触发付费流程或升级到更高套餐。

对于出海产品,硬限制是更安全的选择。AI 模型的调用成本是实时的,超额使用意味着直接的美元支出,不允许「先欠后还」。

二、权限表设计

2.1 权限模型的选择

权限控制有三种常见模型:

模型说明适用场景
ACL(Access Control List)直接给用户分配权限小型系统、权限简单
RBAC(Role-Based Access Control)用户 → 角色 → 权限中大型系统、多角色多权限
ABAC(Attribute-Based Access Control)基于属性的策略引擎超大型系统、复杂策略

对于 AI 产品出海,RBAC 是最平衡的选择。它比 ACL 更灵活,比 ABAC 更简单,能够满足绝大多数 SaaS 产品的权限需求。

2.2 RBAC 核心表结构

RBAC 的经典设计需要五张表:

用户表 users(已存在,此处只列出关键字段):

字段类型说明
idUUID主键
emailVARCHAR(255)用户邮箱
statusVARCHAR(20)账户状态

角色表 roles

字段类型说明
idUUID / SERIAL主键
nameVARCHAR(50)角色名称,如 admineditorviewer
display_nameVARCHAR(100)显示名称
descriptionTEXT角色说明
is_systemBOOLEAN是否系统内置角色(不可删除)
created_atTIMESTAMPTZ创建时间

权限表 permissions

字段类型说明
idUUID / SERIAL主键
codeVARCHAR(100)权限编码,如 image:generateproject:delete
resourceVARCHAR(50)资源类型,如 imageprojectsettings
actionVARCHAR(50)操作类型,如 createreadupdatedelete
descriptionTEXT权限说明
created_atTIMESTAMPTZ创建时间

用户-角色关联表 user_roles

字段类型说明
idBIGSERIAL主键
user_idUUID关联用户
role_idUUID / INTEGER关联角色
granted_atTIMESTAMPTZ授予时间
expires_atTIMESTAMPTZ过期时间(可选,用于临时角色)

角色-权限关联表 role_permissions

字段类型说明
idBIGSERIAL主键
role_idUUID / INTEGER关联角色
permission_idUUID / INTEGER关联权限
granted_atTIMESTAMPTZ授予时间

2.3 权限编码规范

权限编码(code)是代码中实际使用的标识符,设计规范直接影响代码的可读性和可维护性。

推荐采用 resource:action 的二级格式:

image:generate       -- 生成图片
image:delete         -- 删除图片
project:create       -- 创建项目
project:share        -- 分享项目
team:manage          -- 管理团队成员
billing:update       -- 更新账单信息
admin:system_config  -- 系统配置

如果业务更复杂,可以扩展到三级 resource:action:scope,例如 project:read:own(只读自己的项目)和 project:read:all(读取所有项目)。但三级格式会增加复杂度,建议只在确实需要数据范围区分时才引入。

2.4 功能访问控制的实现

在应用层,权限校验通常以中间件或装饰器的形式实现:

// 中间件伪代码
function requirePermission(permissionCode: string) {
  return async (req, res, next) => {
    const user = req.auth.user;
    const permissions = await getUserPermissions(user.id);
 
    if (!permissions.includes(permissionCode)) {
      return res.status(403).json({ error: 'Permission denied' });
    }
    next();
  };
}
 
// 使用
router.post('/api/images/generate', requirePermission('image:generate'), generateHandler);
router.delete('/api/projects/:id', requirePermission('project:delete'), deleteHandler);

性能优化方面,用户权限在登录后缓存到 Redis 或 JWT 的 claims 中,避免每次请求都查询数据库。权限变更时清除对应缓存即可。

三、RBAC 模型的扩展

3.1 角色层级

在实际产品中,角色往往有层级关系。例如 admin 角色自动继承 editorviewer 的所有权限。实现方式有两种:

方案 A:显式继承——在 roles 表中增加 parent_role_id 字段,形成树形结构。查询权限时递归向上收集所有祖先角色的权限。

方案 B:权限聚合——不设层级,在分配角色时直接将所有需要的权限显式关联。管理上稍显冗余,但查询更简单,性能更好。

对于中小规模的 AI 产品,推荐方案 B。角色数量通常不超过 10 个,显式分配带来的冗余完全可以接受,而且避免了递归查询的复杂度。

3.2 多租户与团队权限

出海 SaaS 产品通常需要支持多团队(Workspace / Organization)。此时 RBAC 表需要增加租户维度:

  • user_roles 表中增加 workspace_id 字段,表示用户在某个工作空间中的角色。
  • 同一个用户在不同工作空间可以有不同的角色和权限。
  • roles 表可以是全局的(所有工作空间共享同一套角色定义),也可以是工作空间级别的(每个工作空间可以自定义角色)。

推荐先做全局角色,后续根据客户需求再开放自定义角色的能力。过早引入工作空间级别的自定义角色会显著增加系统复杂度。

3.3 数据权限

RBAC 控制的是功能权限(能不能做某个操作),但有时候还需要控制数据权限(能看到哪些数据)。例如:普通编辑者只能看到自己创建的项目,管理员可以看到所有项目。

数据权限通常在业务查询层实现,而非在权限表中定义。常见做法是在 permissions 表中增加 scope 字段:

scope 值含义
own只能访问自己创建的数据
team可以访问团队成员的数据
all可以访问所有数据

在查询时,根据 scope 动态拼接 WHERE 条件。这种方式简单有效,但要注意 scope 的判断逻辑应该封装在统一的数据访问层,避免散落在各个业务模块中。

四、额度和权限的关联

额度和权限不是孤立的,它们在产品中经常协同工作。典型场景:

  1. 权限决定能不能用,额度决定能用多少:用户必须有 image:generate 权限,并且额度充足,才能发起图片生成请求。
  2. 套餐同时绑定角色和额度Pro 套餐的用户自动获得 editor 角色和每月 5000 次生成额度。
  3. 管理员权限可以调整他人额度:拥有 team:manage 权限的管理员可以给团队成员分配额外额度。

在数据库层面,关联方式有两种:

松耦合:额度表和权限表各自独立,在业务逻辑层做联合判断。请求进来时,先检查权限,再检查额度,两步都通过才放行。

紧耦合:在套餐表(plans)中同时定义角色和额度,用户订阅套餐时自动创建对应的角色关联和额度记录。

推荐松耦合方案。额度和权限的职责不同,变更频率也不同(权限相对稳定,额度随套餐变化),保持独立更利于维护和演进。

五、额度和权限的校验流程

以下流程图展示了用户发起请求时,系统如何联合校验额度和权限:

流程图画布 · 115%
Mermaid 流程图加载中...

关键设计点:

  • 权限校验在额度校验之前:先确认用户有没有资格做这件事,再检查额度是否充足。如果反过来,没有权限的用户也能探测到额度信息,造成信息泄露。
  • 额度扣减在执行之后:如果请求执行失败(如模型调用超时),不应该扣减额度。确保扣减操作在确认成功后执行,或者采用「预扣减 + 确认/回滚」的两阶段模式。
  • 阈值预警:当额度使用达到 80% 或 90% 时主动通知用户,避免用户在不知情的情况下突然被中断服务。

六、实战案例

案例 1:AI 图片生成平台的额度设计

某 AI 图片生成平台提供三种套餐:

套餐月费每月额度刷新策略附加权限
Free$050 次生成每月刷新仅标准画质
Pro$192000 次生成每月刷新高清画质 + API 访问
Enterprise$9910000 次生成每月刷新全部功能 + 团队管理

在数据库层面,用户订阅套餐时:

  1. user_roles 中插入对应角色关联(如 pro_user)。
  2. user_quotas 中创建或更新额度记录,设置 total_amount = 2000refresh_policy = 'monthly'
  3. 每月订阅续费时,刷新额度:重置 used_amount = 0,更新 period_startperiod_end

案例 2:多团队协作的权限设计

某 AI 写作工具的团队权限结构如下:

角色权限
owner所有权限,包括删除团队、管理账单
admin管理成员、管理项目、查看所有数据
editor创建和编辑自己的项目、分享项目
viewer只读访问被分享的项目

实现时,roles 表中预置四个角色,role_permissions 表中预置每个角色对应的权限集合。用户被邀请加入团队时,默认分配 viewer 角色,管理员可以在团队设置中修改成员角色。

这种设计的好处是:权限变更不需要修改代码,只需要在后台调整 role_permissions 的关联关系。新增功能时,只需要在 permissions 表中插入新权限,并将其关联到合适的角色。

七、设计检查清单

在落地额度和权限表设计时,逐项检查以下要点:

  1. 额度表是否区分了额度类型和来源?——免费额度和付费额度应该可以独立追踪。
  2. 额度刷新是否具备幂等性?——同一周期内不会重复刷新。
  3. 额度扣减是否在事务中执行?——避免并发导致的超卖问题。
  4. 超额处理策略是否明确?——硬限制还是软限制,是否有自动升级机制。
  5. 权限编码是否遵循统一规范?——resource:action 格式,易于理解和扩展。
  6. RBAC 表是否支持多租户?——同一个用户在不同团队中可以有不同的角色。
  7. 权限校验是否在额度校验之前?——先验权再验额度,避免信息泄露。
  8. 权限变更是否及时生效?——缓存刷新机制是否到位。
  9. 系统内置角色是否受到保护?——is_system 标记的内置角色不允许删除或修改编码。
  10. 额度预警通知是否配置?——阈值预警是否覆盖 80% 和 90% 的使用节点。
  11. 是否有额度使用日志?——每次扣减都应该有可追溯的流水记录。
  12. 数据库索引是否合理?——user_idquota_typerole_idpermission_id 等高频查询字段是否建立了索引。

八、小结

额度表和权限表是 AI 产品后端基础设施的重要组成部分。额度表解决「用多少」的问题,需要关注额度类型、刷新策略、扣减事务和超限处理;权限表解决「能不能用」的问题,需要关注 RBAC 模型、权限编码规范和多层级扩展。

二者在请求校验流程中协同工作:先验权限,再验额度,通过后执行请求并扣减额度。设计上保持松耦合,通过业务逻辑层进行联合判断,可以让系统更灵活地应对产品迭代。

出海场景下,还需要额外关注多租户隔离、货币单位(Credits vs. 次数 vs. Token 数)和合规要求(GDPR 下的数据访问权限)。这些内容会在后续的国际化与合规章节中展开。


参考资料

  1. Designing a Simple Quota System — LaikaTest 关于可扩展额度系统的设计实践
  2. RBAC user permission database design — 阿里云开发者社区的 RBAC 数据库设计方案
  3. How to Design an RBAC System — NocoBase 的 RBAC 系统设计指南
  4. Best RBAC database model — Stack Overflow 上关于 RBAC 数据库模型的经典讨论
  5. Scalable API Rate Limiting & Quota Management System — 可扩展 API 限流与额度管理系统设计
  6. 经典角色权限系统设计五张表及拓展应用 — 牛客网的 RBAC 五张表设计与拓展讨论
  7. 多租户 SaaS 应用中 RBAC 可扩展设计 — 火山引擎关于多租户 SaaS 权限设计的方案
  8. Database design for credit-based purchases — Stack Overflow 关于 Credits 购买系统的数据库设计讨论