权益 / 套餐 / 配额
受众:开发者 / 运维 摘要:Entitlement 模块提供中性的套餐、权益项、配额策略、用户权益、余额和流水底座,供 Open Platform 与后续业务消费方复用。
spring-open-core-entitlement 是平台化权益底座。它不绑定 Open Platform、AI、会员、商城或阅读等单一业务语义,而是用 consumerCode、itemCode、业务对象和幂等键隔离不同消费方的套餐与配额。
本模块已按 P1 平台化底座推进,不再把额外消费方作为启动前置。实现仍保留中性命名和消费方隔离字段,后续 Open Platform 旧套餐 / 配额能力迁移时只作为其中一个消费方接入。
当前范围
当前版本已冻结数据模型、表结构、基础 contract、用户权益生命周期状态机和配额消耗 contract:
| 能力 | 说明 |
|---|---|
| 套餐目录 | entitlement_package 保存套餐编码、名称、状态、版本和描述 |
| 权益项 | entitlement_item 保存可消费权益,按 consumer_code + item_code 唯一隔离 |
| 套餐权益绑定 | entitlement_package_item 关联套餐与权益项,并承载额度、周期和重置策略 |
| 配额策略 | entitlement_quota_policy 保存权益项的默认额度、周期、重置和透支策略 |
| 用户权益 | entitlement_user_entitlement 保存用户、消费方、来源套餐、状态和有效期 |
| 配额余额 | entitlement_quota_balance 保存用户在某个权益项上的总额、已用、冻结和剩余额度 |
| 配额流水 | entitlement_quota_ledger 保存检查、消耗、释放、退款等幂等流水的中性载体 |
| 内容访问策略 | ContentAccessPolicyService 统一解释免费、登录可见、限免、作者直读、直接购买解锁和权益凭证匹配 |
| 内容解锁凭证 | ContentUnlockCredentialService 以 consumerCode + ownerType + ownerId + itemCode 查询 active 用户权益,支持按 packageCode 或套餐内启用 itemCode 解锁付费 / 会员内容 |
生命周期状态机
用户权益状态由后端统一流转,前端只展示返回的 status、statusLabel、available、effectiveAt、expiresAt 和 frozenAt。
| 状态 | 展示值 | 可用性 | 说明 |
|---|---|---|---|
active | 生效中 | effectiveAt 已到且 expiresAt 未到时可用 | 购买成功或过期后续费恢复 |
frozen | 已冻结 | 不可用 | 运维冻结,保留有效期和来源信息 |
expired | 已到期 | 不可用 | 读取或操作时发现 expiresAt <= now 会自动落库为到期 |
cancelled | 已取消 | 不可用 | 运维取消后的终态,不允许续费恢复 |
购买支持 durationDays 或显式 expiresAt,durationDays 会从 effectiveAt 开始计算。续费支持 durationDays 或显式 expiresAt,按当前有效期叠加,不允许缩短未到期权益。带 sourceType + sourceId 的购买会按来源幂等返回已有权益,带相同来源的续费重复请求不会再次延长。
配额消耗 Contract
配额操作统一按 consumerCode + userEntitlementId + itemCode + quotaScope + quotaPeriod 定位余额。服务会校验用户权益仍为可用状态、权益所属消费方与请求消费方一致、权益项已绑定到套餐、配额策略已启用,并在缺少余额记录时按策略懒创建余额。
| 操作 | 语义 | 结果 |
|---|---|---|
check | 检查余额是否足够,不扣减 | 返回 allowed、failureReason 和当前余额;带幂等键时写入检查流水 |
consume | 扣减可用余额 | 余额不足抛出业务错误;成功后增加已用额度、减少剩余额度并写入消耗流水 |
release | 释放预占 / 已用额度 | 从已用额度回补剩余额度,不允许回补超过已用额度 |
refund | 退款或冲正回补额度 | 与 release 一样回补已用额度,并用独立流水方向保留审计语义 |
idempotencyKey 用于同一方向操作的幂等保护。重复请求会返回已有流水和余额;同一个幂等键跨消费方或跨操作方向复用会被拒绝。
内容访问与解锁凭证
ContentAccessPolicyService 是面向 Blog、Forum、Reading、Video 等内容模块的中性内容访问策略入口。调用方传入消费方、用户、内容作者、访问类型、权益项编码、直接购买解锁标记、限免时间以及可选会员折扣权益项,服务统一返回是否可读、命中原因、可选权益凭证和可选会员折扣资格。
当前统一访问类型包括:
free:公开可读。login:登录后可读。limited-free:限免期内可读。paid/points/vip/member:可通过直接购买 / 积分扣减 / 模块自有订单先行解锁,也可通过 Entitlement 内容解锁凭证兜底解锁。
ContentUnlockCredentialService 负责底层凭证查询。调用方传入消费方、归属用户和内容需要的权益项编码,服务只返回当前仍处于 active、已生效且未过期的用户权益凭证。
当 paid 或 points 内容的完整解锁凭证未命中时,调用方可以额外传入 memberDiscountItemCode 和 memberDiscountRate。如果当前用户命中该会员折扣权益项,决策会返回 readable=false、reason=needs-payment-with-member-discount、discountEligible=true、discountRate 和折扣凭证摘要,表示内容仍需购买 / 积分解锁,但购买链路可以使用会员折扣。vip / member 访问类型仍要求完整会员权益,不会因为折扣资格被误判为可读。
匹配规则:
itemCode可直接匹配用户权益的packageCode,用于“整包会员”解锁。itemCode也可匹配该套餐内启用的entitlement_item.code,用于“套餐内某个内容权益项”解锁。- 冻结、过期、取消、未生效或套餐项禁用的权益不会返回凭证。
Blog、Forum、Reading 和 Video 的内容读侧已通过 ContentAccessPolicyService 复用该服务:Blog 使用 consumerCode=blog、ownerType=user,文章 vipItemCode 可命中 Entitlement 套餐编码或套餐项编码,member / vip 都走同一会员权益凭证;Blog 付费文章还会把保存请求里的 memberDiscountItemCode / memberDiscountRate 写入文章扩展元数据,详情 VO 返回折扣资格字段并由 PC / App 锁定态展示“可享会员价”提示;Forum 使用 consumerCode=forum、ownerType=user,默认 forum-paid-topic / forum-vip-topic 解锁付费 / 会员主题;Reading 使用 reading-paid-chapter / reading-vip-chapter,其中 points 章节先走 Money 积分扣减直接解锁;Video 使用 video-paid / video-vip,member 内容流同样复用会员凭证。业务模块仍保留自己的购买订单、积分扣减、支付回调和退款失效状态机,只把作者直读、直接解锁、限免、权益凭证匹配和折扣资格决策统一交给 ContentAccessPolicyService,后续新增内容付费消费方不再各自复制免费 / 登录 / 作者 / 限免 / 权益匹配逻辑。
Admin 侧提供内容访问审计入口,用于复现某个用户对某个内容的当前可读性决策。运维可传入 consumerCode、ownerType、userId、contentOwnerId、accessType、itemCode、memberDiscountItemCode、memberDiscountRate、directUnlock、limitedFreeUntil、businessType 和 businessId,接口返回 readable、reason、credentialMatched、命中的权益凭证信息、discountEligible、折扣率和折扣凭证摘要。退款撤销或订单失效后,业务模块应把 directUnlock=false 传入该审计入口,确认是否仍能通过 Entitlement 凭证兜底解锁;需要核对会员价时,再传入折扣权益项和折扣率复现用户是否具备折扣资格。
Open Platform 侧提供只读内容访问检查 API,用于第三方服务端在同一 HMAC、scope、限流、调用日志、用量和账单治理边界内复用该策略。接口不创建解锁凭证、不写购买订单、不处理退款撤销,只返回和 Admin 审计一致的当前访问决策;业务侧购买、积分扣减、支付回调和退款状态仍由 Blog / Forum / Reading / Video 等 owning module 处理。
管理端 Contract
当前 Controller 位于 /api/v1/admin/entitlement,用于 Admin 运维入口的第一阶段接入。
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
POST | /api/v1/admin/entitlement/packages | entitlement:package:create | 创建或更新套餐基础资料 |
GET | /api/v1/admin/entitlement/packages/{id} | entitlement:package:view | 查询套餐详情与已绑定权益项 |
POST | /api/v1/admin/entitlement/items | entitlement:item:create | 创建或更新权益项 |
POST | /api/v1/admin/entitlement/packages/{id}/items | entitlement:package:update | 将权益项绑定到套餐 |
POST | /api/v1/admin/entitlement/packages/{id}/items/{itemId}/quota-policies | entitlement:quota-policy:update | 保存套餐项配额策略 |
GET | /api/v1/admin/entitlement/user-entitlements | entitlement:user:view | 按用户与消费方查询用户权益 |
POST | /api/v1/admin/entitlement/user-entitlements/purchases | entitlement:user:purchase | 给指定 owner 购买并生成用户权益 |
GET | /api/v1/admin/entitlement/purchases/audit | entitlement:user:view | 按权益 ID、消费方、归属、套餐、状态或 source 查询购买 / 续费排障信息 |
POST | /api/v1/admin/entitlement/user-entitlements/{id}/renewals | entitlement:user:renew | 按天数或到期时间续费用户权益 |
POST | /api/v1/admin/entitlement/user-entitlements/{id}/freeze | entitlement:user:freeze | 冻结用户权益 |
POST | /api/v1/admin/entitlement/user-entitlements/{id}/unfreeze | entitlement:user:freeze | 解冻用户权益 |
POST | /api/v1/admin/entitlement/user-entitlements/{id}/cancel | entitlement:user:cancel | 取消用户权益 |
POST | /api/v1/admin/entitlement/user-entitlements/{id}/expire | entitlement:user:expire | 手动标记用户权益到期 |
POST | /api/v1/admin/entitlement/quotas/check | entitlement:quota:check | 检查权益配额余额 |
POST | /api/v1/admin/entitlement/quotas/consume | entitlement:quota:consume | 扣减权益配额 |
POST | /api/v1/admin/entitlement/quotas/release | entitlement:quota:release | 释放权益配额 |
POST | /api/v1/admin/entitlement/quotas/refund | entitlement:quota:refund | 退款或冲正回补权益配额 |
GET | /api/v1/admin/entitlement/quotas/ledgers | entitlement:user:view | 分页查询权益配额流水,支持消费方、用户权益、权益项、余额、方向、业务信息、幂等键和时间范围筛选 |
GET | /api/v1/admin/entitlement/content-access/audit | entitlement:user:view | 复现 Blog / Forum / Reading / Video 等内容的访问策略决策,返回可读性、命中原因和权益凭证 |
Admin 页面入口为 /entitlement/entitlements。页面已接入“套餐配置”页签,可按“创建套餐 -> 创建权益项 -> 绑定套餐项 -> 保存配额策略”的顺序完成中性目录配置;创建套餐和权益项成功后会把返回 ID 自动回填到绑定与配额策略表单,也可直接按套餐 ID 读取详情和已绑定权益项确认配置结果。“用户权益”页签支持按消费方、归属和状态查询,并可对非终态权益执行冻结、解冻、取消和手动过期;“购买排障”页签支持直接给指定归属对象购买套餐,购买成功后会带入返回权益和 source 自动执行审计,也可按权益 ID、消费方、归属、套餐、状态或 sourceType/sourceId 查询购买 / 续费来源、命中权益、最近配额流水和 release / refund 类回补流水数量,用于排查套餐购买、续费幂等、退款撤销后权益凭证是否仍命中;“配额操作”页签支持检查、消耗、释放和退回权益配额,写操作要求填写幂等键,执行后展示余额和流水信息;“配额流水”页签支持按消费方、用户权益、权益项、余额、方向、业务类型 / ID、幂等键和发生时间分页检索历史流水;“内容访问审计”页签支持复现内容访问决策,便于排查退款撤销后仍可读或权益凭证未命中的问题。
Open Platform 内容访问 API
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
POST | /api/v1/open/entitlement/content-access/check | open:entitlement:content-access:read | 按 ContentAccessPolicyService 复现内容访问决策,返回可读性、命中原因、权益凭证和会员折扣资格 |
请求体字段与 Admin 内容访问审计一致,常用字段包括 consumerCode、ownerType、userId、contentOwnerId、accessType、itemCode、memberDiscountItemCode、memberDiscountRate、directUnlock、limitedFreeUntil、businessType 和 businessId。调用方必须先在 Open Platform 后台为应用授权 open:entitlement:content-access:read,并按开放平台 HMAC 规则签名。成功调用会进入 Open Platform 调用日志、用量统计和账单治理;接口本身只读,不绕过内容业务自己的购买、支付、退款和积分扣减状态机。
用户侧只读 Contract
当前用户只读入口用于 App / PC 用户中心展示账号维度的权益套餐、配额余额、到期时间和最近消耗流水,不允许用户传入 ownerType、ownerId 或内部状态字段。
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
GET | /api/v1/entitlement/me/entitlements | 当前登录用户 | 查询当前用户 ownerType=user、ownerId=当前用户 ID 的权益概览 |
响应字段:
entitlementCount/availableCount/quotaBalanceCount:当前账号权益、可用权益和配额余额项数量。totalQuota/usedQuota/remainingQuota:当前返回配额余额的汇总额度。entitlements:用户权益列表,包含consumerCode、packageCode、status、statusLabel、available、effectiveAt、expiresAt。quotaBalances:配额余额列表,按用户权益、权益项、scope / period 展示总额、已用、剩余和重置时间。recentLedgers:最近配额流水,支持ledgerLimit查询参数,后端限制最大返回数量。
视图入口:
- PC 用户中心:
/account/entitlements,并从/account常用服务入口进入。 - App 会员中心:
/member/index的权益卡片和概览指标展示当前用户权益和剩余额度。
后续收口
当前 P1 首段已完成中性模型、状态机、配额消耗、Open Platform 迁移适配、Admin 套餐配置、直接购买、购买 / 续费来源审计、用户权益生命周期运维、配额操作入口、独立配额流水检索页、App / PC 用户侧只读入口、内容访问策略、会员折扣元数据、Blog 消费端折扣提示、Admin 内容访问审计和 Open Platform 内容访问检查 API。后续积分解锁策略验证、更多消费方三端展示或复杂营销规则时,应按独立功能簇补充 contract、视图、测试和文档。