ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Activepieces 定时降级席位上限机制解析:scheduledUsersLimit 的派生、执行与自愈设计

Activepieces 定时降级席位上限机制解析:scheduledUsersLimit 的派生、执行与自愈设计 Activepieces 定时降级席位上限机制解析scheduledUsersLimit 的派生、执行与自愈设计【免费下载链接】activepiecesAI Agents MCPs AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces导读本文以 Activepieces 仓库内的架构决策记录 000017-scheduled-downgrades-cap-seats-immediately.md 为骨架结合packages/server/api下的 Autumn 计费集成、席位配额校验与数据库迁移代码系统讲解计划降级scheduled downgrade在切换生效前就立即以目标计划的席位配额封顶这一机制的来龙去脉。读者将掌握为什么需要立即封顶而不是等到账期结束、scheduledUsersLimit如何在现有权益投影同步中派生并自愈、席位消耗类操作邀请、重新激活如何以min(usersLimit, scheduledUsersLimit)为闸门执行、前端计费弹窗与席位卡片如何联动展示以及超额席位即刻退款这一 console 侧编排背后的取舍。文末还会以表格对比被否决的三种备选方案帮助理解该决策的设计边界。一、问题背景账期切换前存在数周的席位窗口漏洞Activepieces 的云版计费由外部计费平台 Autumn 托管通过console.activepieces.com持有主密钥AP 服务端以客户级密钥只读投影权益。在这个模型里套餐降级与取消并回到 Free并不会立即生效——Autumn 会将其排期到当前计费周期的末尾end of billing period才真正切换。席位减少seat decrease不存在这个窗口它们立即生效并按比例退款Autumn 的on_decrease: prorate配置。计划变更plan change则存在窗口从降级发起initiation到周期末切换落地之间往往隔着数天到数周。关键漏洞在于降级发起时ADR-0009 的活跃用户席位下限机制会强制管理员把活跃用户停用deactivate到目标计划的席位数以内——但停用之后没有任何东西阻止管理员在这几周内重新激活用户或邀请新用户。于是周期结束时客户将以超过新计划席位上限的状态抵达切换点陷入要么悄悄超配额、要么在没人盯着的时刻被强制停用的窘境。用一句话概括该 ADR 的核心论断seat decreases dont have this window; scheduled plan changes do.本文要解决的正是这个只有计划变更才有的窗口期。二、决策席位消耗操作立即执行排期席位上限2.1 核心规则在计划变更处于排期scheduled状态期间所有消耗席位的操作邀请用户、重新激活用户都必须同时满足当前计划的席位上限与排期计划的席位上限有效上限 effectiveUsersLimit min(usersLimit, scheduledUsersLimit)即以排期席位上限 scheduledUsersLimit作为即时封顶两个上限取最小值参与校验。2.2 为什么是min而不是usersLimit当前计划如 ProusersLimit 50仍在生效客户还在为其付费排期计划如 StarterscheduledUsersLimit 10即将在周期末接管若只按当前usersLimit校验管理员可以在这几周里把用户数重新拉回到 50周期末必然超 10 的上限取min则把整个窗口期内的席位水位钉死在目标计划额度内切换落地时天然合规。该逻辑在 platform-plan.service.ts 中有直接实现function effectiveUsersLimit({ usersLimit, scheduledUsersLimit }: PickPlatformPlan, usersLimit | scheduledUsersLimit): number | null { const limits [usersLimit, scheduledUsersLimit].filter((limit): limit is number !isNil(limit)) return limits.length 0 ? null : Math.min(...limits) }两个上限均为null即无限时返回null表示不设限任一为有限值时取较小者。三、scheduledUsersLimit是派生值不落库、可自愈、自动清除这是本决策最精巧的一点排期席位上限不降级写入、不单独持久化而是由现有权益投影同步refreshEntitlements在读取 Autumn 客户状态时现场推导出来。3.1 推导来源Autumn 客户状态中的 scheduled 订阅推导入口在 autumn-utils.ts 的toScheduledUsersLimitfunction toScheduledUsersLimit(baseSubscriptions: GetCustomerResponse[subscriptions]): number | null { const scheduledSubscription baseSubscriptions.find((subscription) subscription.status scheduled) const usersLimitItem (scheduledSubscription?.plan?.items ?? []) .find((item) item.featureId UnconsumableFeatureId.USERS_LIMIT) if (isNil(usersLimitItem) || usersLimitItem.unlimited) { return null } return usersLimitItem.included ?? null }要点逐条对应 ADR 的陈述排期基础订阅scheduled base subscription从客户的所有订阅中过滤出status scheduled的那一条——注意排期的是基础订阅toBaseSubscriptions会先剔除addOn附加购买见 autumn-utils.tsUSERS_LIMIT 特征项在排期订阅的 plan items 中找featureId UnconsumableFeatureId.USERS_LIMIT的条目取included套餐内含席位作为上限无限席位返回null若该项为unlimited则视为无封顶。3.2 取消回 Free 走同一条路径cancel-to-Free之所以能被同一套推导覆盖是因为 Autumn 在客户取消后会把自动启用的 Free 计划排期成一条scheduled订阅——ADR 注明verified in sandbox已在沙箱环境中验证。因此取消并回到 Free排期订阅是 Free 计划included为 0 或很小的基础席位scheduledUsersLimit立即收紧计划降级排期订阅是目标付费计划included为目标套餐含席位。3.3 自愈能力覆盖越权降级与清理路径派生值的最大收益是自愈越权out-of-band降级客户可能在 Stripe 自助门户、Autumn 管理后台或通过客服操作support ops直接降级。这些路径不经过 AP 的降级发起逻辑若在发起时落库就会漏掉。派生方案不需要任何降级入口钩子——下次投影同步自然看到新的 scheduled 订阅封顶随之生效自动清除当排期被取消reactivate away或切换已落地applies后Autumn 客户状态里不再有该 scheduled 订阅派生值自动归零/更新无需显式的清理代码路径。3.4 触发时机复用现有刷新链路派生值搭乘现有的refreshEntitlements投影同步链路写入platform_plan表。触发时机包括见 autumn-utils.ts 的refreshEntitlements与 autumn-billing.ts 各入口取消后刷新cancelSubscription之后重新激活后刷新reactivateSubscription之后懒拉取同步15 分钟throttledBillingProviderRefresh通过分布式锁与去重runOnceWithin周期性调用refreshEntitlements见 platform-plan.service.ts此外applyAppSumoPlan、activateLicense、compFreeLegacy等权益变更路径同样以refreshEntitlements收尾保证投影一致。refreshEntitlements的整体流程为读取 Autumn 客户getCustomer展开订阅与购买的计划→toAutumnEntitlements推导权益含scheduledUsersLimit→mapAutumnFeaturesToPlatformPlan映射为PlatformPlanProjection→ 更新platform_plan行 → 写客户状态缓存 → 使 billing overview 缓存失效 → 必要时供给 license key。其中投影映射在 autumn-utils.tsscheduledUsersLimit与usersLimit同层写入return { ...toPlatformPlanFlags(entitlements.grantedFeatureIds), plan: entitlements.planId, billedTeamProjectsLimit: toPlatformPlanLimit(teamProjects, 1), usersLimit: toPlatformPlanLimit(users, null), scheduledUsersLimit: entitlements.scheduledUsersLimit, activeFlowsLimit: toPlatformPlanLimit(activeFlows, null), includedCredits: credits?.granted ?? 0, }3.5 数据库落点该列由迁移 1818000000000-AddAutumnBillingColumnsToPlatformPlan.ts 添加ALTER TABLE platform_plan ADD COLUMN IF NOT EXISTS scheduledUsersLimit integer并在实体 platform-plan.entity.ts 中声明。注意列是投影的落点不是来源——来源永远是 Autumn 客户状态列只是派生结果的一等公民缓存这决定了它天然可重建、可自愈。四、执行端席位消耗操作以有效上限为闸门4.1 邀请在 user-invitation 模块入口拦截邀请用户时user-invitation.module.ts 在事务内调用配额校验await platformPlanService(request.log).checkUsersExceededLimit({ platformId, entityManager, additionalSeatsNeeded })4.2 重新激活堵住最关键的漏洞重新激活INACTIVE → ACTIVE在 user-service.ts 走同一校验。这正是 ADR 强调的核心漏洞点降级弹窗强制停用用户后唯一的逃生通道就是重新激活——如果不堵前面的一切封顶都形同虚设。4.3checkUsersExceededLimit的完整校验链核心实现在 platform-plan.service.tscheckUsersExceededLimit: async ({ platformId, entityManager, additionalSeatsNeeded 1 }: CheckUsersExceededLimitParams): Promisevoid { if (ApEdition.COMMUNITY edition) { return } if (additionalSeatsNeeded 0) { return } if (!await billingProvider.get(log).isBillingEnforced(platformId)) { return } const platformPlan await platformPlanRepo(entityManager) .createQueryBuilder(platform_plan) .setLock(pessimistic_write) .where(platform_plan.platformId :platformId, { platformId }) .getOne() if (isNil(platformPlan)) { return } const usersLimit effectiveUsersLimit(platformPlan) if (isNil(usersLimit)) { return } const { usedSeats } await countUsedSeats({ platformId, log, entityManager }) if (usedSeats additionalSeatsNeeded usersLimit) { throw new ActivepiecesError({ code: ErrorCode.QUOTA_EXCEEDED, params: { metric: PlatformUsageMetric.USERS, }, }) } },逐段解读其工程细节环节实现说明社区版豁免ApEdition.COMMUNITY edition直接返回席位配额仅在企业/云版计费强制场景生效计费强制开关isBillingEnforced(platformId)读取 Redis 中的billingEnforced标志由权益投影写入未强制时放行并发控制setLock(pessimistic_write)对platform_plan行加悲观写锁防止并发邀请/激活同时越过上限有效上限effectiveUsersLimit(platformPlan)min(usersLimit, scheduledUsersLimit)即本文主题已用席位countUsedSeats活跃用户数 保留邀请数usedSeats activeUsers invitedSeats见 platform-plan.service.ts与 ADR-0014 的邀请占位机制一致失败形态QUOTA_EXCEEDEDmetric: USERS与 ADR-0009 相同的错误码前端可统一处理超出席位4.4 与 ADR-0009 的关系请求时校验 DB 权威ADR-0009 确立了席位下限在 AP 服务端以其自有数据库为权威执行enforce DB-authoritatively on the AP serverADR-0017 在此之上追加了排期上限在窗口期即刻生效。二者叠加后的完整语义是加/邀请checkUsersExceededLimit本次决策的落点重新激活同一校验本次决策补上的漏洞降下限降级/取消回 Free/席位减少assertSeatsNotBelowActiveUsers——按目标上限与当前已用席位比较不足则抛QUOTA_EXCEEDED见 platform-plan.service.ts。它在/checkout目标计划含席位时、cancelSubscriptionFree 含席位时与adjustUnconsumableFeatureQuantityUSERS_LIMIT 数量调整三处调用见 autumn-billing.ts。值得注意由于排期封顶的存在周期末切换落地时不会再出现超上限瞬间assertSeatsNotBelowActiveUsers在切换发生时也不会因既有用户数超限而失败——窗口期已被提前封死。五、前端体验弹窗、席位卡片与管理控件的三处联动ADR 同时规定了 UI 层的呈现确保管理员在发起降级前、降级后的窗口期内都能清楚理解约束5.1 超席位弹窗按计费概览状态分支Out of seats席位已满弹窗会检查 billing overview 状态字段——cancelAt取消生效时间与scheduledPlanName排期计划名由 autumn-billing.ts 的toBillingInfo从 scheduled 订阅推导——以决定展示文案普通超限常规购买更多席位引导存在排期降级展示降级专属文案并提供 Keep current plan保留当前计划补救入口让管理员一键取消排期、恢复原上限。5.2 席位卡片反映待生效上限用户Users卡片头部使用effectiveTotal展示当前生效的有效席位并附注说明该上限来自排期计划the limit comes from the scheduled plan避免管理员误以为计费出错的困惑。5.3 管理席位控件排期期间隐藏只要存在排期降级购买额外席位的控件即被隐藏——因为即使买了额外席位也无法在当前窗口期抬高min(...)封顶购买只会浪费。这与已购额外席位在窗口期内不可用的后果一脉相承。六、额外席位的处置降级发起即移除并按比例退款排期封顶带来的直接后果是客户在窗口期内为已购买的额外席位prepaid/purchased seats付了钱却用不上它们无法把min上限顶上去。因此决策规定降级发起downgrade initiation的同时移除额外席位并立即按比例退款prorated credit。具体实现将席位总量set-total恢复为套餐内含额度included allotment复用 Autumn 既有的on_decrease: prorate配置——即席位减少的既有即时退款路径。重新激活reactivation时不恢复这些额外席位。6.1 为什么放在 console 侧而不是 AP 侧这是 ADR 中最明确的职责切分mirrors ADR-0009AP 侧用户数相关的守卫checkUsersExceededLimit/assertSeatsNotBelowActiveUsers以 AP 数据库为权威console 侧所有计费/金钱编排/billing/cancel与/billing/checkouthandler——额外席位移除与退款是纯粹的 entitlements/money 编排不需要任何 AP 数据库知识。console 侧放置还有一个关键收益覆盖所有 API 调用方发起的降级。无论降级来自 AP 前端、控制台工具还是第三方 API 调用只要最终打到/billing/cancel或/billing/checkout退款逻辑就必然执行不存在绕过路径。6.2 Fail-open 原则移除额外席位按失败开放fail-open处理即便退款编排异常也不阻塞降级本身避免把客户卡在既降不了级又退不了款的死局。七、备选方案对比为什么否决另外三条路ADR 明确记录了三组被否决的候选方案对比有助于理解最终设计的取舍逻辑方案思路否决理由发起时落库重新激活/切换时清除降级发起时把排期上限写入 DBreactivate 或 switch 时清除逻辑更直白、可追踪但会静默错过越权降级Stripe 门户/Autumn 后台/客服操作且任何遗漏的清除路径都会留下一个过期上限长期阻塞合法的席位使用允许并警告切换时容忍超限尊重客户仍在付费的席位超限部分在切换时 grandfathered重新打开最初的漏洞切换瞬间要么静默超限要么在没人盯着的时刻被强制停用与 ADR-0009 的目标背道而驰只封顶新邀请仅限制 invite不限制 reactivationreactivation 恰恰是最主要的漏洞通道拆分规则徒增解释负担为何邀请受限而激活不受限且封不住漏洞最终方案本质上是派生 统一闸门不落库规避了越权降级与过期残留min(usersLimit, scheduledUsersLimit)一视同仁地封住邀请与激活用最少的规则闭合最完整的漏洞面。八、边界与已知取舍ADR 在 Consequences 中坦承了几处有意的边界付费到账期结束的客户不能重新激活超过排期上限——这是刻意为之管理员在降级弹窗中已承诺切换到更小计划上限内仍可自由替换用户swap users within the cap且可随时通过 Keep current plan 立即解除限制SCIM 与 managed-authn 用户创建不受此守卫约束——与 ADR-0010 的职责范围保持一致该领域由另外的机制管辖这是明确划出的边界而非遗漏额外席位在窗口期不可用且重新激活不恢复——与即时退款配套避免付了钱却用不上的同时又保留可恢复的复杂账务状态与 ADR-0009 的职责镜像用户数守卫在 APDB 权威计费变更在 console两层各自独立演化、互不耦合。九、延伸阅读席位下限的权威执行设计ADR-0009 active-user seat floor is enforced DB-authoritatively本文决策的直接前序含曾尝试 console 背stop 后被否决的历史背景保留邀请占用席位的机制ADR-0014 pending invitations reserve seatsusedSeats计数的组成依据核心实现platform-plan.service.tseffectiveUsersLimit、checkUsersExceededLimit、assertSeatsNotBelowActiveUsers、countUsedSeats权益投影与推导autumn-utils.tsrefreshEntitlements、toScheduledUsersLimit、toAutumnEntitlements、mapAutumnFeaturesToPlatformPlan计费提供方入口autumn-billing.tstoBillingInfo的scheduledPlanName/cancelAt、三处assertSeatsNotBelowActiveUsers调用数据库迁移1818000000000-AddAutumnBillingColumnsToPlatformPlan.tsscheduledUsersLimit列相关测试packages/server/api/test/integration/cloud/user-invitations/seat-reservation.test.ts、packages/server/api/test/integration/cloud/platform-plan/plan-update-column-isolation.test.ts席位占用与计划列隔离的行为验证【免费下载链接】activepiecesAI Agents MCPs AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表