Civitai 捐赠目标(DonationGoal)设计重构:以"众筹免费 30 天"取代永久解锁的四条规则方案
【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai
导读:本文基于仓库内 docs/creator-studio/monetization/donation-goals.md 的设计文档,完整讲解 Civitai 捐赠目标(DonationGoal)从"达标后永久免费"转向"众筹一个免费月"的四条规则设计,并结合 donation-goal.service.ts、paid-access.service.ts 等源码,验证"捐赠即时到账、无捐赠者退款、达标不锁定"三个关键现状,分析构建成本、决策记录、去留争议与开放问题。读完你将理解该方案的规则骨架、底层实现链路与决策边界,可直接用于评审、实现或二次设计。
该设计文档状态为proposed(提案),尚未实现,并取代了此前探索托管(escrow)与退款的donation-goal-escrow.md。同时需注意:整个 DonationGoal 功能本身也在考虑移除(2026-09-03 Justin 提出,等待社区反馈),因此在动手构建之前必须先决定"是否保留捐赠目标"这一前置问题。
四条规则:整个设计的全部
设计用四条规则完整定义了捐赠目标的行为,刻意不引入转换矩阵、退款路径或任何永久状态:
- 捐赠目标必须建立在提供下载权限(download)的门控之上。不能把目标挂在仅生成(generation-only)的门控上。
- 目标达成 → 模型对所有人免费 30 天。窗口期内禁止任何购买,计时从目标达成的那一刻开始。
- 30 天结束后,创作者可以随意为模型定价。
- 没有目标的门控自由定价。没有目标的早期访问日期只是"预计日期(estimate)",必须如实标注。窗口结束后模型只是解除门控——只有创作者再次设置付费访问,它才会重新被门控。
文档原话:"That is the whole design. There is no transition matrix, no refund path, and nothing is permanent."(这就是整个设计。没有转换矩阵,没有退款路径,没有任何永久状态。)
每条规则在解决什么
规则 1 无需显式规则即封堵了"仅生成"逃生通道。创作者无法把带目标的模型切换为仅生成模式,因为那样目标将不再合法。早期设计需要逐一枚举所有降级路径——去掉下载、涨价、切换为永久——并且总是在漏掉的那条路径上出问题。此处由"目标的前提条件"完成这份工作。
规则 1 还保证了达标目标交付的是持久的东西:用户可以保留的文件。访问记录(EntityAccess)只由购买产生,因此免费窗口在仅生成模型上交不出任何持久权益——30 天用完就什么都没有。规则 1 使这种情况不可能发生。
规则 2 的购买拦截是规则 1 的安全保障。窗口期内没有购买,就没有人在窗口中途获得永久访问权,EntityAccess始终保留"购买"的含义。窗口在两个方向上都是惰性的:不进钱,也不出钱。
规则 3 和 4 是创作者的灵活性,也是整个设计不需要退款的原因。创作者永远不会被困住;他们只是不能收回已经"卖给"社区的这一个月。
创作者付出的代价
达成目标意味着损失 30 天的销售额。因此目标金额至少应等于该模型一个月的收入,否则捐赠者就是替所有人以低价买下了这个月。文档指出:创作者今天没有任何理性方法设定目标金额,这套设计给了他们一个——并且可以从他们自己的销售历史中给出估算值。
现状核实:代码验证的三个事实
创作者线程在第二个事实上得出了相反结论,因此文档用代码核实了三个事实:
事实一:捐赠立即支付给创作者。donateToGoal通过createMultiAccountBuzzTransaction将 Buzz 直接转到goal.userId,无持有期、无中介(见 donation-goal.service.ts)。其中toAccountType: buzzType保证捐赠者用什么颜色(green/yellow)的 Buzz 捐,创作者就收到什么颜色——源码注释记录了此前的缺陷:不指定目标账户类型时,Buzz 服务会应用黄色默认值,导致绿色捐赠到达创作者时变成黄色,自绿色 Buzz 上线以来已产生 137 笔腿账 / 85,210 Buzz。
事实二:不存在捐赠者退款。30 天早期访问退款是存在的,但它退款的是付费访问的购买者,且仅在取消发布(unpublish)时触发(见 model-early-access-refund.service.ts)。捐赠者只有在自己的捐赠交易中途失败时才会被退款(refundMultiAccountTransaction,见 donation-goal.service.ts)。创作者线程假设该保护存在;实际上并不存在。在四条规则下也不需要——没有任何"承诺"可以被打破。
事实三:达标的模型目前并不会保持解锁。endPaidAccessNow的实现就是一条 SQL:UPDATE "PaidAccess" SET "endsAt" = NOW(),仅此而已(见 paid-access.service.ts);下一次编辑器保存会执行writePaidAccessForModelVersion,其permanent分支会写入一个新的门控(endsAt NULL, timeframeDays NULL,见 paid-access.service.ts)。所以今天的现实是:创作者可以收下全额资助的目标,让模型解锁,然后当天下午就重新门控它。规则 2 的 30 天窗口正是针对这种情况必须强制的最低保障。
另外:checkDonationGoalComplete目前只会结束限时(timed)门控(源码中isTimedGateActive判断 +endPaidAccessNow调用,见 donation-goal.service.ts),因此挂在永久付费访问上的目标是惰性的——捐赠者资助的东西按构造就解锁不了任何东西。规则 2 通过把"解锁"变成目标的属性(而非门控的属性)修复了这一点。
从代码实现还可以看到捐赠链路的完整性约束(对应 donation-goal.router.ts):
- 捐赠入口为 tRPC 的
donationGoalRouter.donate,需要TokenScope.SocialTip作用域并禁止 API Key。 - 输入校验(donation-goal.schema.ts)只有
amount: number与donationGoalId: number。 - 业务规则:Blue Buzz 禁止捐赠、目标必须 active、不能向自己的目标捐赠。
- 目标达成的处理顺序是:先提交捐赠(
donation.create),再尽力而为地关闭目标并结束限时门控;门控结束的缓存清理是 fail-open 的(Redis 抖动只记录日志,绝不回滚已提交的捐赠),而目标关闭与门控结束的 DB 写入是 fail-closed 的(真实写失败必须抛出)。这一区分被 donation-goal.service.test.ts 的 7 个用例逐条验证(未达标不关目标、ComicChapter 实体不触发 ModelVersion 缓存清理、fail-open 的缓存清理、fail-closed 的 DB 写入等)。
"切换确实发生过"的证据
writePaidAccessForModelVersion把永久门控写为endsAt NULL, timeframeDays NULL。但仍有 63 条永久门控带着timeframeDays——这个残留与"限时门控经某条未清理该字段的路径切换为永久"一致。
| 63 条之中 | 数量 |
|---|---|
| 带有捐赠目标 | 31 |
| ——收了钱 | 15· 15,200 Buzz · 62 位捐赠者 |
| —— 目标全额达成 | 1 |
⚠️ 注意:这是证据而非证明,且是下限而非精确计数。另一条写路径也可能留下该字段,而任何清除了该字段的切换都不可见。真实数字 ≥ 15。
构建成本:比任何早期方案都小
因为什么都不持有、什么都不永久,成本小于此前所有选项:
- 门控上的免费窗口——
goalMetAt <= now < goalMetAt + 30 days解析为免费,无论门控本身怎么说。只需要在解析路径上加一个区间判断。 - 购买守卫—— 窗口开启期间拒绝购买。
- 目标前提条件—— 目标只能在门控授予下载权限的地方创建。
- 文案—— 预计日期要读起来像预计日期,免费月要读起来像一个月。
不需要托管(escrow)、不需要退款路径、不需要终态、不需要持有余额、不需要回填在途资金。
已决事项
| 事项 | 决定 |
|---|---|
| 未达成的目标 | 对捐赠者什么都不发生。创作者保留捐赠。这正是当前的工作方式,也是绝大多数情况——值得以后重新审视,但不是现在。 |
| 窗口期结束 | 解除门控。不是终态。只有创作者再次设置付费访问才会重新门控。 |
| 免费月何时开始 | 目标达成的那一刻,而非原来的免费日期。 |
| 永久性 | 30 天是唯一保证的免费。不存在永久免费承诺。 |
| 托管(escrow) | 搁置(2026-09-03)。不追求持有捐赠并退款。四条规则下没有任何东西需要它。 |
🔴"没有任何东西是终态的"取代了此前的决定(此前认为达标目标和窗口期结束都应让模型永久免费)。以上规则是当前立场。
是否保留捐赠目标:一个必须先做的决定
截至 2026-09-03 在考虑中,等待社区反馈。两件事使这个决定不同寻常,且都反对进行大规模投票。
移除目标会消解本文档要解决的问题
自创作者线程以来的一切,都是在管理捐赠所创造的义务。没有捐赠就没有义务:早期访问日期变成一个如实标注的预计日期,规则 4 就是全部设计。这不是支持移除的论据——而是先解决移除的理由:先建规则 1–3 再砍掉整个功能,等于全部白做。
收入高度集中
截至 2026-09-03 的 90 天度量:238 位创作者收到捐赠,共19.7M Buzz。
| 占全部捐赠收入比例 | |
|---|---|
| 前 10 名 | 78.0% |
| 前 50 名 | 95.0% |
| 长尾 | |
|---|---|
| 中位数创作者,每季度 | 5,015 Buzz |
| 75 分位 | 16,693 Buzz |
| 低于 10k Buzz | 238 人中的 147 人 |
| 低于 1k Buzz | 53 人 |
| 高于 100k Buzz | 21 人 |
(刻意省略单创作者数据——本仓库是公开的,且人群足够小,个人收入可被反推。)
🔑所以大规模投票会误导。它抽样到的绝大多数是每季度只有几千 Buzz 利益的创作者,而承载 78% 收入的约 10 人只是频道里的十个声音。结果会是真诚的、无代表性的,并且会被读作授权。
收入对话应该与大约 20 个人直接进行——直接对话,而不是投票。
但旁边有一个真实的社区问题
捐赠者面很广,即使收入方不广。仅过去 30 天,就有2,201 位不同捐赠者在门控目标上捐款。所以"人们喜欢捐赠目标吗"和"移除它会在物质上伤害任何人吗"有不同的答案,一次线程会把两者模糊化。
去问捐赠者他们会用什么替代;去问头部创作者目标为他们赚到了什么别的东西赚不到的。并注意框架陷阱:"我们应该保留捐赠目标吗"会诱发对现状的辩护。14.5% 的完成率暗示诚实的替代品可能是一个打赏罐(tip jar),而不是解锁机制。
四条规则恰好落在同一小群体上
前 10 名中有 6 位、前 50 名中有 40 位捐赠收入者使用带目标的限时门控(143/238 总体)。所以这不是一项广泛的政策变更——它改变的是约十来个头部创作者的操作方式,而"达成目标要付出 30 天销售额"的代价恰好砸在钱最集中的地方。这是另一个应该去和他们谈而不是宣布的理由。
开放问题
D1 —— 折扣阶梯末端的"免费"是什么意思?paid-access-decay.md 建立在PaidAccessGuarantee之上:一个不可撤销的、永久的免费日期——freeAt只能提前、永不清除、是所有读取的上限。"30 天是唯一保证的免费"的决定与之直接矛盾。要么那个保证也变成 30 天窗口,要么产品对"免费"一词有两种不同含义——阶梯的含义与目标的含义。在从任何一份文档开始构建之前,这一点必须解决。
D2 —— 周期可以重复吗?门控 → 目标 → 免费月 → 重新门控 → 新目标。读起来没问题,可能还不错——一个循环的社区解锁——但它应该被决定,而不是被偶然发现。
D3 —— 14 个活跃的仅生成目标。规则 1 会让它们失效。建议:让它们在旧条款下运行到完成,并阻止新的。追溯性地使目标失效改变了已经向其捐赠的人的约定,而这正是本设计要阻止的行为。
D4 —— 提前达成目标会让捐赠者损失免费时间吗?免费月从目标达成算起,所以一个 15 天窗口第 3 天达成的目标,其免费期比第 15 天达成的更早结束。这是作为决定的一个属性被记录的,而不是重新讨论:快速资助的捐赠者得到的是更早的月份,而不是更长的月份。
度量数据(2026-09-03 度量)
| 指标 | 数值 |
|---|---|
| 门控版本上的捐赠目标 | 1,997 |
| —— 门控包含下载 | 1,981(99.2%) |
| —— 仅生成(规则 1 将禁止) | 16,其中 14 个活跃 · 53,260 Buzz |
| —— 限时门控上、已获资助 | 764 · 4.5M Buzz |
| 使用中的早期访问窗口 | 3–15 天(提供了 30 天,未被资助目标使用) |
| 过去 30 天在限时门控上有捐赠的创作者 | 119 · 2,514,562 Buzz · 2,201 位捐赠者 |
| 捐赠目标(历史累计) | 22,841 ·86.9% 已获资助·14.5% 达成 |
规则 1 编码了创作者已经在做的事:99.2% 的目标挂在授予下载权限的门控上。
86.9%/14.5% 的拆分是"未达成目标什么都不做"成为主流情况而非边缘情况的原因——大约16,500 个目标收了钱却从未解锁任何东西。这就是现状,上述决定保持了它。
考虑过并放弃的方案
保留仅为避免从头重推推理。
- 托管(Escrow)—— 把捐赠持有在系统账户中(像赏金那样),承诺被撤回时退款。2026-09-03 搁置,而非仅仅被比下去:除了 30 天窗口外没有承诺任何东西,就没有什么可退的,而且它会把每位诚实创作者的提现延迟约一周。完整的探索——赏金先例、约 1 周的持有、五步构建和实测的浮存——在
donation-goal-escrow.md中,本文档取代了它。如果未来重提托管,从 git 历史中找回它;不要重新推导。 - 永久保证—— 达标目标或窗口结束让模型永久免费。被"30 天是唯一保证的免费"的决定放弃。
- 转换矩阵—— 按配置允许或禁止每次门控转换,目标收了钱的地方就退款。因组合爆炸而放弃;规则 1–4 无需网格就覆盖了同样的情况。它唯一持久的思想——授权集与价格分开保护——以规则 1 的形式存活下来。
实现侧补充:与现行代码的对应关系
对打算基于本文档实现的人来说,以下几点对应关系值得留意(均已在上面源码中引证):
- 捐赠入口与校验:
donationGoalRouter.donate(donation-goal.router.ts)+donateToGoalInput(donation-goal.schema.ts)。 - 捐赠到账与目标关闭:
donateToGoal/checkDonationGoalComplete(donation-goal.service.ts),其中"目标是否达成"由SUM("amount")聚合决定,未达标不关闭目标、不触碰门控(有对应单测)。 - 门控写入与结束:
writePaidAccessForModelVersion/endPaidAccessNow(paid-access.service.ts),永久门控写为endsAt NULL, timeframeDays NULL,这正是文档"63 条残留"证据的比对基准。 - 退款边界:仅付费购买者的 unpublish 退款(model-early-access-refund.service.ts),与"捐赠者无退款"的现状一致。
- 目标显示过滤:
getDonationGoals仅展示"存在活跃限时窗口且创作者未隐藏"的目标(donation-goal.service.ts)——规则 1 若实现,还需在此类读路径上同步增加"目标只能挂在含下载门控上"的约束。
四条规则方案的价值在于:用"目标的前提条件"替代"对每种降级路径逐一枚举",用"30 天窗口"替代"永久承诺 + 托管 + 退款",把一个组合爆炸的转换问题压缩成一次区间判断、一次购买拦截、一次前提校验和几句诚实的文案。在动手之前,唯一真正需要回答的是:捐赠目标这个功能本身是否还要存在。
【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考