客户分层授权模型:方案、限制与标志
设计一个包含方案、限制和标志的清晰授权模型,让管理员和支持在无需工程介入的情况下安全调整客户访问权限。

为什么团队需要授权模型
如果你出售不止一个层级,迟早会收到这样的支持工单:“客户 X 付了 Pro,但无法访问功能 Y。”没有清晰系统时,支持无法直接修复。一次简单的访问变更就会变成工程任务。
更大的问题是不一致。访问规则会散落在产品各处:管理界面里的一个复选框、API 中的硬编码检查、电子表格里的备注,以及上季度的一次性数据库更新。客户在不同地方看到不同的行为,没人确定哪个规则才是唯一的真实来源。
授权模型为你提供一个基于客户方案和任何已批准例外的单一真实来源,用来判断谁能做什么。它让层级表现可预测(这样定价才可信),同时也为现实情况留出空间:临时升级、配额提升,或为某个账户开放的试点功能。
“无需工程即可调整”应该是具体可行的。实际上意味着:
- 支持在管理工具里通过编辑数据来更改访问,而不是请求一次部署。
- 产品在各处读取相同的授权数据(后端、Web 应用、移动端)。
- 例外可以有时间限制并可撤销,而不是永久的 hack。
- 更改有日志记录,包含是谁、何时、为什么。
例如,某个 Business 层级客户在旺季超出了活跃用户数限制。支持应该能授予 14 天的 +10 席位,系统在期限结束后自动回滚。只有当你在添加全新功能时才需要工程介入,而不是在例行的访问调整上。
基本要素:客户、方案与授权
好的授权模型从几个清晰的对象和明确的职责开始。如果这些基础不清晰,支持会每周向工程请求“再一个例外”。
下面是一组简单的构建块:
- Customer (account/tenant): 使用你产品的公司或个人。
- Subscription: 商业关系(试用、激活、取消),通常与计费系统关联。
- Plan: 命名层级(Free、Pro、Enterprise),定义默认访问。
- Entitlement: 实际允许的行为,由方案加上任何覆盖决定。
授权评估不是计费。计费回答“我们应该什么时候收多少钱?”,授权回答“这个客户现在能做什么?”。一个客户可能未付款但在宽限期内,或者已付费但因合规被临时阻止。将这些决策分开,让财务可以修发票而不误改产品访问。
多个团队依赖这个设置:
- 产品定义方案的含义。
- 支持需要安全的控制来授予或移除访问。
- 销售运营需要对成交和续约有一致规则。
- 财务需要在售出内容与提供访问之间有可靠映射。
早期就设定边界。让方案内容和客户覆盖可配置(以便支持能操作),但把核心行为保留在代码里。所谓“核心行为”包括你如何计算剩余额度、如何处理过期试用,以及哪些行为必须被审计。
标志、限制与配额:选对类型
当你正确命名授权时,大多数分层问题会变得更容易。有三种常见类型,每种回答不同问题:
- Boolean flags: 完全开或关?例如:export_enabled = true。
- Numeric limits: 一次允许多少?例如:max_seats = 10。
- Quotas: 在一段时间内可以使用多少?例如:api_calls_per_month = 100000。
标志适用于不应部分工作的功能。如果导出关闭,就隐藏按钮并阻止端点。限制适用于不会重置的“容量”设置,比如席位、项目或保存的视图。
配额需要额外注意,因为时间很重要。当重置规则在管理界面中被写明并可见时,支持工单会大量减少。
范围也是防止混淆的决策之一。像“SAML SSO enabled”这样的标志通常是账号级的。"Max projects" 可能是工作区级的。如果你按角色出售附加项,"Can run reports" 可能是用户级的。
对于配额,每个配额选一个重置规则并坚持:
- Never(终身额度)
- Monthly(日历月)
- Rolling window(最近 30 天)
- Per billing period(与发票周期一致)
如果不同方案的重置规则不同,就把该规则本身视为授权内容,而不是部落知识。
一个实用的授权数据库模式
对支持友好的授权模型通常保持简单:几张表、清晰键,以及可审计的有时限记录。目标是让管理员通过编辑数据来更改访问,而不是部署代码。
从四张核心表开始:plans、plan_entitlements、customers 和 customer_overrides。
- Plans 描述层级(Free、Pro、Enterprise)。
- Plan entitlements 描述每个方案包含什么。
- Customers 指向某个方案。
- Overrides 覆盖单个客户的例外,而不改变整个方案的定义。
一个紧凑的关系形态:
plans:id,name,description,is_activeplan_entitlements:id,plan_id,key,type,value,unit,reset_policy,effective_from,effective_to,created_bycustomers:id,name,plan_id,status,created_atcustomer_overrides:id,customer_id,key,type,value,unit,reset_policy,effective_from,effective_to,created_by
授权字段在各表间应保持一致。使用稳定的 key,例如 seats、api_calls 或 sso_enabled。用 type 简化评估(例如:flag、limit、quota)。明确存储 unit(如 users、requests、GB)。对于配额,保持 reset_policy 的含义明确(如 monthly、daily、never)。
覆盖应表现为带日期的白名单。如果客户有一个在生效期内的 sso_enabled=true 覆盖,它应该覆盖方案值,但仅在 effective_from 和 effective_to 范围内生效。这就使“授予客户 10 个额外席位 14 天”成为一条会自动过期的单行更改。
授权评估应如何工作
授权评估是一段小代码(或服务),回答一个问题:“这个客户现在被允许做这件事吗?”如果这一部分可预测,其他一切都更容易操作。
使用清晰的优先级并不要偏离:客户覆盖 > 方案值 > 系统默认。这样支持可以在不改动方案的情况下授予临时例外,也给工程一个在未配置时的安全默认值。
一个实用的评估流程:
- 从认证会话中识别客户/账户(不要从请求体里识别)。
- 加载客户的活动方案以及任何活动覆盖。
- 对于某个键,若有覆盖则返回覆盖;否则返回方案值;否则返回系统默认。
- 如果某键在任何地方都不存在,对于强制访问检查应“关闭失败”(视为“不被允许”),对于仅展示的 UI 使用合理的默认值。
- 如果键未知(不在你的注册表中),把它视为配置错误,关闭失败并记录以便跟进。
缓存很重要,因为授权会被频繁校验。为每个客户缓存解析后的授权,使用短 TTL 和显式版本号。在发生这些变化时使缓存失效:方案分配、方案定义、客户覆盖或客户状态(试用、宽限、封禁)。一个简单模式是“按 customer_id + entitlements_version 缓存”,支持编辑时提升版本号以便快速反映更改。
多租户安全是不可谈判的。每个查询都必须按当前客户/账户 id 过滤,且每个缓存条目都必须以该 id 为键。不要仅凭邮箱、域名或方案名查找授权。
逐步流程:一个对支持友好的调整访问工作流
一个对支持友好的工作流让模型灵活,同时不会把每个边缘情况都变成工程任务。目标是安全地做更改、留有记录,并确认客户体验。
一个安全的支持流程
首先找到正确的客户记录并确认他们的请求和原因。"需要再多两个席位一周"和"我们签了一个提高层级的补充协议"是不同的请求。一个好的管理 UI 应该能在一个页面清晰展示当前方案、客户状态和任何活动覆盖。
在更改前,检查实际使用量是否达到当前限制或配额。很多请求在看到账号并未达到上限或问题出在别处(例如使用跟踪未更新)时就会消失。
需要调整访问时,优先创建明确的覆盖而不是编辑方案。保持覆盖范围窄(一个标志或一个限制),包含负责人和理由,并默认设置到期日。临时例外很常见,但也容易被遗忘。
管理工具中的一个简单清单通常就够了:
- 确认客户身份、当前方案和请求理由。
- 对照相关上限查看当前使用量。
- 应用有范围的覆盖并设置到期时间。
- 添加备注并关联工单或案例编号。
- 使用模仿或测试账号在产品 UI 中验证结果。
始终以客户的体验方式验证更改。如果支持提供模仿功能,要明显标注何时启用了模仿并记录日志。
升级、降级、试用和宽限期
大多数授权问题在变更时出现:客户在计费周期中途升级、银行卡扣款失败,或试用在周末结束。如果规则含糊,支持会猜测,工程会被拉入处理。
对于升级,保持简单:访问通常应立即变化,而计费细节留在计费系统。你的授权模型应监听像“plan changed”这样的计费事件并立刻应用新方案的授权。如果计费做了按比例分摊很好,但不要把分摊逻辑写进授权中。
降级则是容易出现惊喜的地方。选定清晰的降级行为并对支持可见:
- Grace period: 在付费期结束前保持更高访问。
- Read-only: 允许查看/导出数据,但禁止新写入。
- Hard stop: 立即阻止功能(仅用于高风险功能)。
- Over-limit behavior: 允许使用,但一旦超额就阻止创建新项。
- Data retention: 保留数据但禁用访问直到升级。
把试用当作独立的方案,而不是客户上的一个布尔值。为试用方案设置明确的标志和限制,并添加自动到期规则。试用结束后,将客户移动到默认方案(通常是“Free”)并应用你定义的降级行为。
宽限期对于付款失败也很有用。短期的“逾期”窗口(例如 3 到 7 天)能给团队时间修复支付问题而不会在工作日中断访问。把宽限期视为有时限的覆盖,而不是自定义方案名。
一个实用建议:不要把授权绑定到像“Pro”或“Enterprise”这样的营销层级名。保持稳定的内部方案 ID(例如 plan_basic_v2),这样你可以重命名层级而不破坏规则。
可审计性与安全控制
如果支持可以在没有工程参与的情况下更改访问,你需要审计线索。一个好的授权模型把每次更改当作可记录的决策,而不是无声的微调。
对每个覆盖,记录操作者、业务理由和时间戳。如果组织需要,为敏感更改增加审批步骤。
每次更改应记录的内容
保持日志简单以便实际使用:
created_by和created_atapproved_by和approved_at(可选)reason(简短文本,如“付费附加”或“事故赔偿”)previous_value和new_valueexpires_at
安全控制在事故到达生产之前拦截它们。在管理界面和数据库中设置护栏:限定最大值、阻止负数,并在大幅更改时(例如把 API 调用提高 10 倍)要求到期时间。
回滚与审计准备
支持会犯错。提供一个“恢复到方案默认”的单一操作,清除客户级覆盖并将账户返回到分配的方案。
为审计准备历史导出功能,按客户和日期范围导出。包含理由与审批人的基础 CSV 往往能回答大多数问题,而无需工程介入。
示例:某 Pro 客户需要为为期一周的活动增加 30 个席位。支持设置 seats_override=60 并把 expires_at 设为下周五,备注“事件,已销售 Alex 批准”。到期后系统自动回到 30 席位,并保留完整的记录以便计费时查证。
让授权变得痛苦的常见错误
破坏授权模型最快的方法是任其无意间膨胀。几个早期的捷径会在数月内转成支持工单和“为什么该客户能做这事?”的火线问题。
一个常见的问题是把功能检查散落在各处。如果不同部分的应用以不同方式决定访问,你就会发布互相矛盾的行为。将授权评估集中在一个函数或服务后面,并让所有 UI 与 API 调用都使用它。
另一个常见陷阱是把计费状态与访问混为一谈。"已付费"不等于"被允许"。计费有重试、退款、试用和事后结算等情况。把计费事件分离,并用清晰规则把它们翻译为授权(包括宽限期),这样边缘情况不会把用户锁出或永远放行。
避免仅依赖一个像 "basic" 或 "pro" 的单一层级字符串作为唯一来源。层级会随时间改变,例外也会出现。存储明确的标志和限制,让支持能在不意外授予层级全部权限的情况下只开放单项能力。
手动覆盖是必要的,但无限制的覆盖和无护栏会成为不可见的技术债务。要求覆盖具有负责人、理由和工单参考。鼓励设置到期或复审日期。保持覆盖窄小(一次只改一个键),并让它们易于审计。
当重置规则含糊时,配额也会出问题。明确“每月”意味着什么(日历月 vs 滚动 30 天)、在升级时如何处理以及未使用的配额是否结转。把这些规则在后端逻辑中强制执行,而不仅仅依赖 UI,这样支持更改就不会在 Web 与移动端产生不一致行为。
上线前的快速检查表
在推出授权模型前,和每天会用它的人一起再检查一遍:支持、客户成功以及值班的负责人。
确保每个功能都映射到一个稳定的授权键,并有明确的负责人。避免重复键如 reports_enabled 与 reporting_enabled。确保每个方案对你发布的键都有明确默认值。如果某键缺失,安全失败(通常是拒绝访问)并在内部告警以便修复。
对于运维,确认工作流确实可用:
- 支持能在不写 SQL 的情况下查看生效访问(方案默认加上覆盖)。
- 覆盖被记录,包含谁更改了什么、理由以及何时到期。
- 配额有可见的重置规则和显示当前使用量的清晰方式。
一个现实测试:让支持给单个客户授予 14 天的附加,然后撤销。如果他们能在两分钟内自信地完成,你就很接近了。
示例场景:带临时例外的分层
假设你提供三个层级,每个层级设置一些会在产品中展示并由后端强制执行的清晰授权:
- Free: 1 个项目、3 个用户、每月 200 次导出、基础 API 限流、7 天审计日志。
- Team: 10 个项目、25 个用户、每月 2,000 次导出、更高的 API 限流、30 天审计日志。
- Business: 无限制项目、200 个用户、每月 10,000 次导出、最高 API 限流、180 天审计日志、启用 SSO。
现在一个 Team 客户说:“我们在季度末需要在本月内进行 8,000 次导出,能帮忙 30 天吗?”这正是临时覆盖比把他们升到新方案更合适的时候。
支持打开客户记录,添加一个覆盖比如 export_monthly_limit = 8000,并把 expires_at 设为今天起 30 天后。他们备注:“Alex(销售)批准,Q4 报表 30 天例外。”
从客户角度,应发生两件事:
- UI 反映了新上限(例如,使用仪表和“剩余导出次数”标签更新)。
- 导出一直工作,直到他们在本月达到 8,000 次为止。
如果超过,他们会看到明确信息,如:“导出次数已达上限(8,000/月)。请联系支持或升级以提高上限。”
到期后,覆盖自动停止生效,客户回落到 Team 方案的限制,无需任何人记得去手动关闭它。
下一步:实现并迭代,同时不拖慢支持
从把“功能”转成一个小的授权目录开始。给每项一个清晰的键、类型(标志 vs 限制 vs 配额)和每个方案的默认值。这个目录成为产品、支持与工程之间的共同语言,名称要具体且稳定。
决定在哪里强制执行。一个安全的规则是:对任何会改变数据或产生成本的操作在 API 层强制执行,使用后台任务在超限时停止长时间运行的工作,并把 UI 作为引导(禁用按钮、友好提示)而非唯一的闸门。
把第一个版本做紧凑。聚焦那些产生最多问题的授权项,为高风险动作添加检查,并交付一个在一个页面展示客户、方案、覆盖与历史的管理视图。
如果你想在不手写代码的情况下快速构建管理面板和底层逻辑,AppMaster (appmaster.io) 很适合这种工作:你可以把方案和覆盖建模为数据,把检查实现为业务流程,并发布一个在后端与应用间保持一致的支持 UI。
常见问题
授权模型是一种基于客户方案及任何已批准例外,统一决定客户当前能做什么的方式。它能避免“UI 可以但 API 报错”的情况,因为系统各处都会读取相同的规则。
如果没有清晰的授权系统,支持会频繁提交工程变更请求来做小的访问调整,客户在不同页面和端点会看到不一致的行为。随着时间推移,规则会散落在代码、管理面板复选框、电子表格和一次性数据库更新中,导致停机和计费争议更容易发生。
计费回答的是“我们应该什么时候收取多少钱”,而授权回答的是“现在允许做什么”。将二者分离可以让财务修复发票和重试逻辑时,不会意外地更改产品访问权限。
当能力应该完全开启或完全关闭时使用标志,例如开启 SSO。对于不会重置的容量类资源(如最大席位数或项目数),使用限制。对于随时间使用的配额(例如每月导出次数),使用配额,并明确重置规则。
选择与产品售卖与执行方式相匹配的范围:像 SSO 这样的通常是账号级,像项目这样的共享资源是工作区级,像基于角色的权限或个人附加项是用户级。关键是在所有检查点使用相同的范围。
常见的默认优先级是:客户覆盖优先,其次是方案值,最后是系统默认。如果键缺失或未知,在强制检查时应拒绝访问,并记录为配置错误以便修复。
一个实用的设计是把方案默认放在一张表,把客户特定的例外放在另一张表,二者使用相同的稳定键和类型。让覆盖有开始和结束时间,这样支持可以授予会自动到期的临时访问,而无需额外清理工作。
为每个客户缓存解析后的授权,使用短 TTL 和版本号。当支持更改方案分配或覆盖时,提升版本号让客户能迅速看到更新,同时避免长时间的缓存失效造成的延迟。
默认做法是创建一个有到期时间和明确理由的窄覆盖,然后用客户视角验证结果。避免为一次性需求修改方案,因为那会影响同一层级的所有客户,并且更难审计。
记录是谁做了更改、何时做的、更改原因、之前的值、新值以及何时到期。并提供一键“回退到方案默认”的动作,这样错误可以快速撤销而无需手工清理。


