笔记
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)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
趋势
从按需付费到纯套餐模式:一次功能开关模型的迁移实践
先说说背景。我们有个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模式:
- Expand:先把新模型建好
- Migrate reads:先切读,再切写
- Migrate data:补数据
- Contract:确认没人用了再删旧代码
四个阶段,每个阶段都能独立部署,每个阶段系统都正常运行。多花这四步的代价,比一个计费事故导致用户无法访问自己付费的功能要小得多。
最后说一句:这种迁移最怕的就是着急。慢一点,稳一点,比什么都强。