墨穗app.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v1.0.165 · 墨穗笔记
笔记

Notebase墨穗
静水流深,落墨成穗。

0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →

笔记

0
加载中...

工具

0

此页用于记录用户反馈问题后的每一次改进

关于

笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

不想公开、只想临时给人看:点“分享”生成一条带密码和有效期的链接,到期自动失效,你也能随时撤销。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势

// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

从按需付费到纯套餐模式:一次功能开关模型的迁移实践

2026-07-07编程开发

先说说背景。我们有个SaaS产品,原来做的是“按功能订阅”——用户想要哪个功能,就单独付那个功能的钱。听起来很灵活对吧?但实际上维护起来简直是噩梦。每次判断用户能不能用某个功能,都得查两个数据源:一是用户买了哪些独立功能,二是用户当前是什么套餐。也就是说,权限判断的逻辑有两套真相来源,每次都得写个“或”逻辑。

所以决定砍掉这种模式,改成纯套餐制:你买一个套餐,套餐里自带哪些功能,就给你哪些功能。听起来简单,但问题是——怎么在不炸掉线上服务的前提下,完成这个迁移?

最忌讳的做法

最容易踩的坑是什么?就是直接把老字段删了,一把梭上线。你要是真这么干,所有正在进行的订阅、所有功能权限检查、所有还在用老数据格式的webhook,全部一起炸。用户付了钱却用不了功能,那可就是事故了。

四阶段迁移计划

我分了四个阶段来做,每个阶段都可以独立部署,部署完系统照样跑。

阶段 做什么 为什么这个顺序
F1 预置套餐数据 新模型必须先存在,后续才能读
F2 权限判断改为基于套餐 读切换到新逻辑,写仍然双写
F3 迁移已有组织到套餐 补数据,确保没人留在旧模型上
F4 删除按功能订阅的旧代码 确认没人用了再删,安全收尾

这其实就是数据库迁移里经典的“expand/contract”模式,只不过这次应用在业务逻辑上。

  • Expand(F1):在旧数据旁边加新数据
  • Migrate(F2–F3):先把读切到新逻辑,再补数据
  • Contract(F4):确认旧数据没人用了,再删

画个图更清楚:

F1 预置套餐     F2 基于套餐判断     F3 回填所有组织     F4 删除旧代码
┌────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ 套餐存在 │ --> │ 读: 查套餐    │ --> │ 每个组织都有  │ --> │ 删除按功能   │
│         │     │ 写: 双写      │     │ 真实套餐      │     │ 订阅代码     │
└────────┘     └──────────────┘     └──────────────┘     └──────────────┘
  安全           安全                安全                 安全

F1:预置套餐数据

这一步其实很简单——在数据库里把套餐定义好。比如“Starter”套餐包含基础功能,“Business”套餐包含高级功能等等。这些套餐数据先存在,后续代码才能引用。

F2:权限判断改为基于套餐

这是最关键的一步。原来判断用户能不能用某个功能,代码可能是这样的:

// 旧逻辑:查两套数据
$hasFeature = $org->hasFeatureSubscription($feature) || $org->plan->grants($feature);

现在改成只问一个问题:这个组织的套餐是否授予了这个功能?

final class EnsureFeatureAccess
{
    public function handle(Request $request, Closure $next, string $feature)
    {
        $plan = $request->user()->organization->currentPlan();
        
        abort_unless($plan?->grants($feature), 403, 'Upgrade required.');
        
        return $next($request);
    }
}

注意这里有个细节:$plan?->grants($feature) 用了PHP 8的null安全操作符。如果组织还没套餐(还在迁移过程中),返回null,那就直接block掉。这样设计是故意的——宁可让还没迁移完的组织暂时用不了新功能,也不能让没付费的人钻空子。

两个边界情况

1. 按量计费的功能

像邮件发送、证书签发这种功能,不是简单的开/关。套餐给的是一个额度(比如每月1000封邮件)。所以权限判断不能只看“有没有这个功能”,还得查剩余配额够不够。

2. 内部组织

我们自己运维团队用的组织(superadmin拥有的),需要永久授予最高套餐权限,这样运维工具永远不会触发付费墙。这个逻辑单独处理,不跟普通组织走同一个流程。

关于支付的那些坑

做这种迁移,最烦人的往往不是业务逻辑本身,而是支付网关的各种小毛病。我遇到了几个:

  • 订单号超长:支付网关要求订单号不超过30个字符,但我们的订单号生成逻辑没考虑这个限制
  • 空手机号导致支付失败:有的用户没填手机号,但支付网关的校验规则不允许null值,导致每次支付都报错
  • 吞掉的校验错误:某些校验失败被静默处理了,没有抛异常,导致排查问题时要翻半天日志

这些bug平时可能影响不大,但做迁移的时候,所有组织几乎同时重新订阅,这些小问题就会集中爆发。所以建议在迁移前,先把支付流程里的所有异常路径都走一遍测试。

F3:迁移已有组织

这一步就是跑个脚本,把还在用旧模式的组织分配到一个合适的套餐。逻辑很简单:

// 伪代码示意
foreach ($orgs as $org) {
    if ($org->hasPaidFeatures()) {
        // 根据用户已购功能,匹配最合适的套餐
        $plan = PlanMatcher::bestMatch($org->subscribedFeatures());
        $org->assignPlan($plan);
    } else {
        // 免费用户给最低套餐
        $org->assignPlan(Plan::free());
    }
}

F4:删除旧代码

确认所有组织都已经迁移到套餐模式后,就可以把跟按功能订阅相关的代码、数据库字段、中间件全部删掉了。这一步最爽,但也最危险——一定要确认没有遗漏。

测试策略

权限判断的逻辑测试其实很简单,就两个核心场景:

it('blocks a feature the plan does not grant', function () {
    $org = Organization::factory()->onPlan('starter')->create();
    
    actingAs($org->owner)
        ->get('/reports/advanced')
        ->assertForbidden();
});

it('allows a feature the plan grants', function () {
    $org = Organization::factory()->onPlan('business')->create();
    
    actingAs($org->owner)
        ->get('/reports/advanced')
        ->assertOk();
});

第一个测试:starter套餐没有高级报表功能,所以请求应该返回403。
第二个测试:business套餐有高级报表功能,所以应该返回200。

这两个测试覆盖了最核心的行为。其他边界情况(比如组织没有套餐、配额不足等)可以单独加测试。

总结

当你改变权限判断模型的时候,要像做数据库迁移一样对待它——即使它实际上是业务逻辑。用expand/contract模式:

  1. Expand:先把新模型建好
  2. Migrate reads:先切读,再切写
  3. Migrate data:补数据
  4. Contract:确认没人用了再删旧代码

四个阶段,每个阶段都能独立部署,每个阶段系统都正常运行。多花这四步的代价,比一个计费事故导致用户无法访问自己付费的功能要小得多。

最后说一句:这种迁移最怕的就是着急。慢一点,稳一点,比什么都强。

相似推荐
uBlock Origin 开发版被 Chrome 商店拒绝:一场关于扩展单一用途政策的争议逆向工程实战:我如何绕过亚马逊Kindle网页端的DRM加密Heroku的丑陋秘密:云之王如何背离Rails并欺骗客户Firefox 成为最后一个支持 uBlock Origin 的主流浏览器:广告拦截的终局之战GitHub Codespaces 深度解读:云端开发环境如何重塑编码工作流认知负荷才是关键:软件设计的根本度量
编写使用方法
Markdown 格式 · Ctrl+Enter 确定
0 字新建笔记
欢迎回来
登录你的墨穗笔记账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属墨穗笔记
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

隐私提醒

取消
编辑工具
受控分享
为这篇笔记生成限时 / 带密码的临时链接
关闭