[{"data":1,"prerenderedAt":1063},["ShallowReactive",2],{"\u002F2026-04-28-helpplay-27":3,"\u002F2026-04-28-helpplay-27-rel":477},{"id":4,"title":5,"body":6,"column":461,"date":462,"description":12,"extension":463,"hero_image":464,"meta":465,"navigation":466,"path":467,"seo":468,"series_id":464,"severity":464,"stem":469,"summary":470,"tags":471,"__hash__":476},"posts\u002F2026-04-28-helpplay-27个模块设计文档两天产出.md","两天，27 个模块的设计文档",{"type":7,"value":8,"toc":446},"minimark",[9,13,21,25,28,80,83,86,93,98,101,139,142,146,149,182,185,206,209,219,232,238,249,253,256,283,286,290,293,299,302,305,308,312,319,322,325,328,391,394,397,403,421,427,433,436,443],[10,11,12],"p",{},"去年 4 月底，地陪服务平台脚手架期启动一周后，需要在两天内产出 27 个业务模块的设计文档。10 项细分服务、多角色分账模型（平台\u002F商家\u002F代理\u002F分销\u002F地陪）、完整后台，每个模块一个独立设计文档——如果逐个手写，进度撑不住。",[10,14,15,16,20],{},"这篇记录批量产出这类文档的方法论。核心思路是",[17,18,19],"strong",{},"先定统一的文档模板与字段规范，再定跨模块共享的核心模型，最后按模板填充各模块","。要点在于：共享模型必须先定死，因为改一次就要改所有相关文档。",[22,23,24],"h2",{"id":24},"先定文档模板骨架",[10,26,27],{},"27 个模块的设计文档虽然内容不同，但结构可以统一。我设定的骨架如下：",[29,30,31,38,44,50,56,62,68,74],"ul",{},[32,33,34,37],"li",{},[17,35,36],{},"1. Goal","：这个模块解决什么",[32,39,40,43],{},[17,41,42],{},"2. Recommended Approach","：为什么采用这个方案而不是其他（这很关键，很多团队会漏掉）",[32,45,46,49],{},[17,47,48],{},"3. Scope","：包含什么、不包含什么（防止后续蠕虫式功能膨胀）",[32,51,52,55],{},[17,53,54],{},"4. Information Architecture","：如果涉及后台，菜单信息架构怎么组织",[32,57,58,61],{},[17,59,60],{},"5. Page Design","：UI 分区、筛选项、表格列、统计卡片等",[32,63,64,67],{},[17,65,66],{},"6. Data Model","：数据库表结构、字段、枚举、索引",[32,69,70,73],{},[17,71,72],{},"7. API Design","：接口地址、参数、响应格式",[32,75,76,79],{},[17,77,78],{},"8. Testing Strategy","：前后端各自怎么测",[10,81,82],{},"这个骨架对所有 27 个模块都适用。没有骨架时，每个模块文档的结构都不一样，审查和理解成本翻倍；有骨架后，新增模块只需要按位置填空。",[22,84,85],{"id":85},"定死五个跨模块共享的核心模型",[10,87,88,89,92],{},"更关键的是",[17,90,91],{},"定死核心数据模型","，特别是账本和订单。一旦这些模型后改，27 个文档都要返工。",[94,95,97],"h3",{"id":96},"_1-订单模型","1. 订单模型",[10,99,100],{},"订单需要覆盖所有场景：用户下单、服务提供者接单、支付、取消、改期、超时计费、位置追踪、评价。这 10 项服务都公用一套订单模型，字段只增不减。",[10,102,103,104,108,109,108,112,115,116,108,119,122,123,108,126,108,129,108,132,108,135,138],{},"核心字段：",[105,106,107],"code",{},"orderId","、",[105,110,111],{},"userId",[105,113,114],{},"escortId","（服务提供者）、",[105,117,118],{},"serviceId",[105,120,121],{},"status","（状态机）、",[105,124,125],{},"amount",[105,127,128],{},"createdAt",[105,130,131],{},"startTime",[105,133,134],{},"endTime",[105,136,137],{},"cancelledAt"," 等。",[10,140,141],{},"最容易踩的坑：不要为了\"简洁\"就给每种服务拆一个订单表。一开始拆，后面统计、对账、消息推送都是灾难。",[94,143,145],{"id":144},"_2-账本模型最复杂","2. 账本模型（最复杂）",[10,147,148],{},"这是文章的重点。27 个模块里，账本类模块有 5 个：",[150,151,152,158,164,170,176],"ol",{},[32,153,154,157],{},[17,155,156],{},"Platform Ledger","（平台收支）",[32,159,160,163],{},[17,161,162],{},"Merchant Ledger","（商家明细）",[32,165,166,169],{},[17,167,168],{},"Agency Ledger","（代理商明细）",[32,171,172,175],{},[17,173,174],{},"Distributor Ledger","（分销商明细）",[32,177,178,181],{},[17,179,180],{},"Escort Ledger","（地陪\u002F达人明细）",[10,183,184],{},"再加上 3 个交易流水类：",[150,186,188,194,200],{"start":187},6,[32,189,190,193],{},[17,191,192],{},"Recharge Records","（充值）",[32,195,196,199],{},[17,197,198],{},"Refund Records","（退款）",[32,201,202,205],{},[17,203,204],{},"Withdrawal Accounts","（提现账户）",[10,207,208],{},"这 8 个都遵循同一套流水结构，共通字段有：",[210,211,216],"pre",{"className":212,"code":214,"language":215},[213],"language-text","{\n  ledgerRecordId,      \u002F\u002F 唯一标识\n  role,                \u002F\u002F 账本角色 (user\u002Fescort\u002Fmerchant\u002Fdistributor)\n  direction,           \u002F\u002F 方向 (income\u002Fexpense)\n  amount,              \u002F\u002F 金额\n  remark,              \u002F\u002F 备注\n  orderNo,             \u002F\u002F 源单号（可追溯）\n  recordedAt,          \u002F\u002F 记账时间\n  createdAt,\n  updatedAt\n}\n","text",[105,217,214],{"__ignoreMap":218},"",[10,220,221,222,108,225,108,228,231],{},"角色特定字段会有差异（比如 Merchant Ledger 多了 ",[105,223,224],{},"paymentMethod",[105,226,227],{},"balanceAfter",[105,229,230],{},"sceneType"," 等），但这套基础字段集合对所有 8 个账本都适用。",[10,233,234,237],{},[17,235,236],{},"最关键的决策：每个角色独立一张表，不要合并。"," 很多人会想\"都是账本，能不能用一个大表加 role 字段区分？\"不行。原因有三：",[29,239,240,243,246],{},[32,241,242],{},"查询效率：同一个角色的流水集中在一张表，索引策略清晰；混在一张表里，where 条件变复杂",[32,244,245],{},"扩展性：未来某个角色的流水逻辑变复杂，新加字段只影响这张表",[32,247,248],{},"权限隔离：后台按角色展示不同的账本页面，数据表和权限边界对齐，防止权限越界的 Bug",[94,250,252],{"id":251},"_3-单笔订单如何拆成多条流水多角色分账","3. 单笔订单如何拆成多条流水（多角色分账）",[10,254,255],{},"这是设计中最容易出错的地方。用户下单一笔，金额是 100 元。这 100 元要在 5 个账本里都体现：",[29,257,258,263,268,273,278],{},[32,259,260,262],{},[17,261,156],{},"：+100（收入）",[32,264,265,267],{},[17,266,162],{},"：-70（服务提供者从平台结账）",[32,269,270,272],{},[17,271,180],{},"：+60（地陪佣金）",[32,274,275,277],{},[17,276,168],{},"：+5（所属代理分成）",[32,279,280,282],{},[17,281,174],{},"：+5（所属分销分成）",[10,284,285],{},"一笔订单，拆成 5 条流水，分别计入 5 个账本。这不能在前端聚合，必须在订单产生时就由 order-service 通过 MQ 广播给各个 ledger 消费者异步处理。",[94,287,289],{"id":288},"_4-幂等键设计","4. 幂等键设计",[10,291,292],{},"MQ 消息可能被重复消费。幂等键设计决定了\"重复消费时是否产生重复的流水记录\"。",[10,294,295,296],{},"常见的幂等键结构：",[105,297,298],{},"{orderId}-{ledgerRecordType}-{direction}-{roleId}",[10,300,301],{},"比如：\"order-12345-commission-income-escort-789\"",[10,303,304],{},"这个 key 存到 Redis，TTL 设为 24 小时。消费者拿到消息后，先检查这个 key 是否存在。如果存在，说明之前处理过，直接返回；如果不存在，才生成一条新的 ledger_record。",[10,306,307],{},"不这样做的后果：用户投诉\"我下单了，怎么平台账户进账两次\"，查日志发现订单服务发了两条相同的 MQ 消息，或者消费者重启期间消息重新投递了。",[94,309,311],{"id":310},"_5-对账口径","5. 对账口径",[10,313,314,315,318],{},"\"对账\"是财务部门关心的。账本数据必须能对应到源单据。每条流水都要有 ",[105,316,317],{},"orderNo"," 字段（或其他源单号），这样财务人员可以随时拿着订单号查到对应的账本记录。",[10,320,321],{},"后台的\"平台收支\"页面，最关键的操作是能按订单号检索流水。如果没有这个，财务无法核账。",[22,323,324],{"id":324},"按模板批量填充",[10,326,327],{},"定好模板和核心模型后，填充就快了。以 Merchant Ledger 为例，从模板复制到文档，然后：",[150,329,330,336,342,348,357,363,373,385],{},[32,331,332,335],{},[17,333,334],{},"Goal"," 部分：改成\"查看商家账户余额流水\"",[32,337,338,341],{},[17,339,340],{},"Recommended Approach","：解释为什么独立一张表而不是从商家余额字段反推",[32,343,344,347],{},[17,345,346],{},"Scope","：列出这个模块包括查询、删除，不包括批量删除、导出",[32,349,350,353,354],{},[17,351,352],{},"Information Architecture","：菜单路由 ",[105,355,356],{},"\u002Ffinance\u002Fmerchant-ledger",[32,358,359,362],{},[17,360,361],{},"Page Design","：筛选项（商家、支付方式、时间）、表格列（商户名、手机号、金额、余额、时间等）",[32,364,365,368,369,372],{},[17,366,367],{},"Data Model","：表名 ",[105,370,371],{},"merchant_ledger_records","，字段集合",[32,374,375,378,379,108,382],{},[17,376,377],{},"API Design","：",[105,380,381],{},"GET \u002Fadmin\u002Ffinance\u002Fmerchant-ledger",[105,383,384],{},"DELETE \u002Fadmin\u002Ffinance\u002Fmerchant-ledger\u002F:id",[32,386,387,390],{},[17,388,389],{},"Testing Strategy","：参数转发、筛选正确、金额计算正确",[10,392,393],{},"从模板到完成，一个模块 20-30 分钟。27 个模块，按这个节奏，两天内完全可以产出。",[22,395,396],{"id":396},"避免的陷阱",[10,398,399,402],{},[17,400,401],{},"陷阱 1：过度个性化","。如果每个模块都要求\"独特的设计\"，模板就没用了。要求统一：所有后台列表页遵循同一个 UI 结构，只改列和筛选项。",[10,404,405,408,409,412,413,416,417,420],{},[17,406,407],{},"陷阱 2：字段命名不一致","。A 账本叫 ",[105,410,411],{},"ledgerRecordId","，B 账本叫 ",[105,414,415],{},"recordId","，C 账本叫 ",[105,418,419],{},"id","。查询时容易出错。从一开始就定好规范，全部遵循。",[10,422,423,426],{},[17,424,425],{},"陷阱 3：索引规划滞后","。数据模型写完了，才想起来没有索引。到了后期\"查询慢\"才加索引，可能已经影响了代码逻辑。索引要在设计文档里就列出来。",[10,428,429,432],{},[17,430,431],{},"陷阱 4：忽略 Not Included","。设计文档里不写\"不包含什么\"，后续就有人问\"为什么没有批量删除\"\"为什么没有导出\"。明确列出 Scope 的边界，能省很多周期。",[22,434,435],{"id":435},"总结",[10,437,438,439,442],{},"批量产出设计文档的要诀是",[17,440,441],{},"复用而非重复编写","。文档模板保证了结构统一，核心数据模型保证了逻辑一致，两者结合，27 个模块的设计工作量从\"无法估算\"变成\"可预测\"。",[10,444,445],{},"最后的提醒：这套方法对脚手架期这样\"框架先行、业务后补\"的场景特别有效。但如果业务需求本身就不清楚，文档多了也没用——反而会变成\"写了一堆高保真但实际没用的文档\"。务必确保需求先冻结，再启动文档产出。",{"title":218,"searchDepth":447,"depth":447,"links":448},2,[449,450,458,459,460],{"id":24,"depth":447,"text":24},{"id":85,"depth":447,"text":85,"children":451},[452,454,455,456,457],{"id":96,"depth":453,"text":97},3,{"id":144,"depth":453,"text":145},{"id":251,"depth":453,"text":252},{"id":288,"depth":453,"text":289},{"id":310,"depth":453,"text":311},{"id":324,"depth":447,"text":324},{"id":396,"depth":447,"text":396},{"id":435,"depth":447,"text":435},"工程手记","2026-04-28","md",null,{},true,"\u002F2026-04-28-helpplay-27",{"title":5,"description":12},"2026-04-28-helpplay-27个模块设计文档两天产出","批量产出设计文档的方法——统一模板、共享核心模型、按模板填充。27 个业务模块若逐个手写撑不住进度，通过先定死文档骨架和跨模块通用数据结构，可以降低重复劳动。",[472,473,474,475],"文档工程","设计模板","模块化设计","分账系统","EmM4jsQgKaOyTtIZh6WmNxTg1Gre2siFrTEdsAHNqKI",[478,784,928],{"id":479,"title":480,"body":481,"column":461,"date":771,"description":485,"extension":463,"hero_image":464,"meta":772,"navigation":466,"path":773,"seo":774,"series_id":464,"severity":464,"stem":775,"summary":776,"tags":777,"__hash__":783},"posts\u002F2026-07-29-一年八千次提交AI辅助开发工作流.md","一年八千次提交，我是怎么干的",{"type":7,"value":482,"toc":754},[483,486,489,492,496,499,502,505,509,512,515,529,532,536,539,542,545,549,552,555,575,578,582,585,596,599,603,606,620,623,626,630,633,636,640,643,646,649,653,656,659,683,690,693,700,703,706,709,716,719,722,748,751],[10,484,485],{},"2026 上半年，主要项目累计提交 7787 次（其中 newCodex 因为是 fork 项目，1922 次提交含上游历史；纯新增代码的项目是 yun-claude、yun-claw、new-openclaw 等）。提交数看起来多，但每个提交背后的价值不在数量，而在流程的可重复性。这篇文章把工作流写清楚。",[22,487,488],{"id":488},"流程的六个环节",[10,490,491],{},"整个开发周期分六步：需求澄清 → 设计文档审阅 → 拆实施计划 → 分阶段实现 → 代码审查 → 故障归档。每一步都有明确的输入输出和验证方式。",[94,493,495],{"id":494},"_1-需求澄清","1. 需求澄清",[10,497,498],{},"开始前问清楚，不要猜。典型问题：这个功能要处理哪些用户场景？边界条件是什么？现有系统的哪部分会受影响？",[10,500,501],{},"这一步的产出是一份结构化的需求文档，包括功能范围、约束条件、风险假设。不是长篇幅的铺垫，而是一份清单式的澄清记录。",[10,503,504],{},"在 new-openclaw 项目里，这一步通常是用 Claude 的 superpowers:brainstorming skill 来展开。提问方式很重要：我会列出已知条件，然后问\"这个设计下会遇到什么问题？\"而不是\"你觉得应该怎么做？\"让 AI 在我的约束框架内工作。",[94,506,508],{"id":507},"_2-设计文档与人工审阅","2. 设计文档与人工审阅",[10,510,511],{},"这是整个流程里最关键的检查点。花一小时审一份 200 行的设计文档，比花五小时审一份 2000 行的代码便宜得多。而且回头修改设计的成本远低于修改实现。",[10,513,514],{},"new-openclaw 项目现有 30 份设计文档（specs 目录），每份文档都包括：",[29,516,517,520,523,526],{},[32,518,519],{},"范围：这个设计覆盖什么、不覆盖什么",[32,521,522],{},"决策与权衡：为什么选这个方案，放弃了什么",[32,524,525],{},"接口契约：如果涉及多个模块，清晰定义每个边界",[32,527,528],{},"风险清单：已知的坑和防护措施",[10,530,531],{},"写完设计文档以后，我会读一遍、问几个\"为什么\"，然后提出修改意见。这一步排除了 80% 的方向错误。常见的修改方向有：缩小范围（第一个版本不用处理那么多边界情况）、明确约束（系统资源、网络延迟、并发数的假设）、补充防护（熔断、限流、幂等）。",[94,533,535],{"id":534},"_3-实施计划与-dod-定义","3. 实施计划与 DoD 定义",[10,537,538],{},"设计文档定下来以后，拆成实施计划。计划的粒度是\"一个可验证的功能单元\"——通常是一个小时到半天的工作量，完成后能单独验证成功。",[10,540,541],{},"计划文档里必须写清完成定义（DoD，Definition of Done）。不是\"实现登录功能\"，而是\"写出能拒绝无效格式的登录端点、覆盖单点故障下的重试、有端到端的冒烟测试\"。",[10,543,544],{},"new-openclaw 项目现有 36 份实施计划（plans 目录），跨度从一周的 S0 阶段（骨架 + Mock 后台）到数周的 S1 阶段（真实业务后台）。每份计划都带着清晰的 checklist，这样我在执行时能随时问 Claude：\"下一步应该是什么？\"而不是脑子里模糊地记着进度。",[94,546,548],{"id":547},"_4-分阶段实现与逐步验证","4. 分阶段实现与逐步验证",[10,550,551],{},"有了计划以后，按顺序实现。关键是每一步都要有验证：单元测试、集成测试、或者一个小的 end-to-end 冒烟测试。验证不通过就停在这一步，不往下推。",[10,553,554],{},"这一步会用到三个代理：",[29,556,557,563,569],{},[32,558,559,562],{},[17,560,561],{},"superpowers:test-driven-development"," —— 先写测试，再写实现",[32,564,565,568],{},[17,566,567],{},"superpowers:subagent-driven-development"," —— 复杂任务拆成独立的子任务，并行推进",[32,570,571,574],{},[17,572,573],{},"superpowers:systematic-debugging"," —— 遇到测试失败，用这个代理追根溯源，不要盲目修改代码",[10,576,577],{},"实施过程中如果发现设计假设错了（比如某个接口响应时间远超预期，或者并发场景下出现竞态条件），就停下来回到第 2 步重新审视设计，而不是继续往下推。",[94,579,581],{"id":580},"_5-代码审查","5. 代码审查",[10,583,584],{},"实现完成以后，不是立即合并，而是过一遍 code-reviewer 代理。审查的重点不在代码风格（那个自动工具做），而在：",[29,586,587,590,593],{},[32,588,589],{},"这段代码实现的是设计文档里的哪一部分？偏离了吗？",[32,591,592],{},"错误处理有没有遗漏？边界情况有没有考虑？",[32,594,595],{},"有没有意外改动无关的代码？",[10,597,598],{},"审查通常能抓住两类问题。一类是逻辑问题：某个条件判断漏了一个分支，或者并发场景下两个操作的顺序反了。另一类是\"设计和实现对不上\"：实现了一个设计里没提到的特性，或者某个约束（比如\"这个值不能为空\"）没在代码里强制。",[94,600,602],{"id":601},"_6-故障归档","6. 故障归档",[10,604,605],{},"系统上线以后，bug 是难免的。重要的是怎么处理它。new-openclaw 项目有一套 bug 知识库规范（CLAUDE.md 里定义），每个 bug 修好以后都要新建一份独立文档，包括：",[29,607,608,611,614,617],{},[32,609,610],{},"现象和复现路径",[32,612,613],{},"根因分析：为什么会发生，触发链路是什么",[32,615,616],{},"修复方法：改了什么，为什么选这个方案",[32,618,619],{},"预防措施：代码改进、测试添加、还是构建期检查",[10,621,622],{},"80 份 bug 文档（截至 5 月中旬）不是问题的多，而是追根溯源的记录的多。下次遇到类似现象，能直接查库而不是重新排查。",[22,624,625],{"id":625},"三条铁律",[94,627,629],{"id":628},"_1-假设必须显式声明","1. 假设必须显式声明",[10,631,632],{},"不要猜。不清楚的地方就问，把问题写成澄清清单。\"这个 API 能处理多大的请求体？\"、\"离线场景下要缓存多长时间？\"、\"错误重试间隔是指数退避还是固定时间？\"。",[10,634,635],{},"这些问题看起来小，但决定了实现的复杂度和测试用例的多少。猜错了会导致前期设计精美，但方向错误，后面要推倒重来。",[94,637,639],{"id":638},"_2-修改必须精确","2. 修改必须精确",[10,641,642],{},"只改需要改的部分。这听起来像常识，但在实际工作中容易出现\"顺手改一下边上的代码\"的情况 —— 格式不规范了，变量命名不一致了，某个函数太长了，\"顺便\"重构一下。",[10,644,645],{},"结果是一个改动影响了五个文件，代码审查花了双倍时间，引入了新 bug 的风险。",[10,647,648],{},"规则是：改动必须对应需求的某一行。格式、风格、无关的重构，单独立项，不要混在功能改动里。",[94,650,652],{"id":651},"_3-成功标准必须可验证","3. 成功标准必须可验证",[10,654,655],{},"\"添加验证\"这个说法是模糊的。改写成：\"写出一个测试用例，输入非法邮箱格式，验证 API 返回 400；输入合法邮箱，验证返回 200 和预期数据结构\"。",[10,657,658],{},"每个 plan 文档里的 DoD 都是这样写的。拿一个阶段做例子：",[660,661,662,665],"blockquote",{},[10,663,664],{},"S0 阶段的 DoD：",[150,666,667,670,677,680],{},[32,668,669],{},"openapi\u002Fapi-v1.yaml 包含 spec 全部 11 个端点的字段级 schema，可被 swagger-ui 加载 ✓",[32,671,672,673,676],{},"backend\u002F Mock 服务 ",[105,674,675],{},"npm start"," 后能响应全部端点，返回符合契约的 mock 数据 ✓",[32,678,679],{},"launcher\u002F Rust 项目能编译为 launcher.exe，运行后完成所有阶段扫描并输出 diagnostics.json ✓",[32,681,682],{},"至少一个 end-to-end 冒烟测试通过（launcher 上报 → mock 后台收到 → 审计日志记录） ✓",[10,684,685,686,689],{},"每一条都能通过一个具体的命令来验证。\"能工作\"太模糊，\"运行 ",[105,687,688],{},"npm test"," 且所有测试通过\"才是可验证的。",[22,691,692],{"id":692},"流程失效的情况",[10,694,695,696,699],{},"这套流程在一个关键点会失效：",[17,697,698],{},"需求本身没想清楚时","。",[10,701,702],{},"我遇到过的例子是这样的。客户说\"要支持 USB 存储检测\"，这个需求很清楚，所以设计文档写了 20 多页，规划了三个阶段，列出了 30 多个 test case。然后两周后，客户补充说\"哦对了，还要处理网络驱动器\"。",[10,704,705],{},"这时前面的设计和计划都要回头改。检测逻辑复杂了，测试场景翻倍，阶段划分要调整。这不是流程的问题，这是需求的问题。流程本身反而帮助我及时暴露了这个风险 —— 如果没有设计文档，可能要到代码审查阶段，甚至系统上线以后才发现这个遗漏。",[10,707,708],{},"应对办法是在第 1 步（需求澄清）多花时间。列出你想到的所有场景，问\"还有其他我忽略的情况吗？\"。不是要求完全预测未来，而是把已知的不确定性显式写出来，而不是假设需求是固定的。",[10,710,711,712,715],{},"另一个失效的情况是",[17,713,714],{},"设计和实现的沟通不畅","。如果设计文档是给另一个人读的（或者给 AI 代理读的），但执行者没有理解透彻，实现出来会偏离设计。预防办法是在开始实施前，再过一遍设计文档，确认\"我清楚要做什么\"。",[22,717,718],{"id":718},"提交数字背后的故事",[10,720,721],{},"为什么能积累 7787 次提交？不是因为每次都在写新功能。真实的分布大概是：",[29,723,724,730,736,742],{},[32,725,726,729],{},[17,727,728],{},"功能实现","：40%",[32,731,732,735],{},[17,733,734],{},"设计文档编写和迭代","：25%",[32,737,738,741],{},[17,739,740],{},"测试编写","：20%",[32,743,744,747],{},[17,745,746],{},"bug 修复与回归测试","：15%",[10,749,750],{},"关键是这些提交都有上下文。每个提交的 message 都指向一个设计文档或一个 plan 的某个环节，或者一个 bug 记录。下次有问题要追查根因时，能快速定位到那个提交，看当时的设计决策是什么。",[10,752,753],{},"另外，有 80 份 bug 记录和 66 份设计 + 计划文档（30 specs + 36 plans）这件事本身说明了一点：文档不是负担，文档是工作的实际产出。代码只是文档的一个落地形式。",{"title":218,"searchDepth":447,"depth":447,"links":755},[756,764,769,770],{"id":488,"depth":447,"text":488,"children":757},[758,759,760,761,762,763],{"id":494,"depth":453,"text":495},{"id":507,"depth":453,"text":508},{"id":534,"depth":453,"text":535},{"id":547,"depth":453,"text":548},{"id":580,"depth":453,"text":581},{"id":601,"depth":453,"text":602},{"id":625,"depth":447,"text":625,"children":765},[766,767,768],{"id":628,"depth":453,"text":629},{"id":638,"depth":453,"text":639},{"id":651,"depth":453,"text":652},{"id":692,"depth":447,"text":692},{"id":718,"depth":447,"text":718},"2026-07-29",{},"\u002F2026-07-29-ai",{"title":480,"description":485},"2026-07-29-一年八千次提交AI辅助开发工作流","从需求澄清到故障归档，一套系统化的 AI 辅助开发流程：先设计文档过审，再分阶段实施验证，最后代码审查与故障存档，三条铁律支撑整个流程。",[778,779,780,781,782],"Claude","工作流","AI 辅助开发","代码审查","文档驱动","DN7sqbGmg2UybSvRXTMLW-dj0UURXET3KsSNeW_1V9I",{"id":785,"title":786,"body":787,"column":461,"date":915,"description":791,"extension":463,"hero_image":464,"meta":916,"navigation":466,"path":917,"seo":918,"series_id":464,"severity":464,"stem":919,"summary":920,"tags":921,"__hash__":927},"posts\u002F2026-07-20-远程桌面管理器DPAPI与60万次迭代.md","密码，不该由我保管",{"type":7,"value":788,"toc":909},[789,792,796,799,802,805,819,822,826,829,832,835,838,841,852,855,858,861,867,873,879,889,903,906],[10,790,791],{},"远程服务器资料管理工具需要妥善保存账户凭据。这个工具采用两层加密设计：本机存储用 Windows DPAPI，备份使用基于密码的密钥派生。两层各有边界，理解这些边界对使用决策至关重要。",[22,793,795],{"id":794},"第一层dpapi-的便利与代价","第一层：DPAPI 的便利与代价",[10,797,798],{},"本机存储的凭据使用 Windows DPAPI（Data Protection API）加密，由当前 Windows 用户身份保护。这是 Windows 内置的用户级加密机制，密钥由操作系统管理，与登录用户的 SID 和机器的本地安全数据库绑定。不需要用户记一个额外的主密码，启动应用即可直接使用保存的凭据。",[10,800,801],{},"DPAPI 的好处显而易见：启动应用即用，无需输入密码，用户体验最优。代价是它的加密密钥锁定在当前用户、当前机器。凭据无法直接迁移到另一台电脑或另一个 Windows 用户——这不是工具的限制，而是 DPAPI 的设计约束。Windows 操作系统就是这样设计的，其他工具也无法绕过。",[10,803,804],{},"实际操作中的含义很明确：",[29,806,807,810,813,816],{},[32,808,809],{},"重装 Windows 前必须先导出备份。原有凭据会因为用户 SID 变化和机器密钥更新而无法解密，即便登录同一账户也不行。",[32,811,812],{},"更换电脑前需要先创建备份并妥善保存备份密码，目标电脑上导入时需要重新输入这个备份密码。",[32,814,815],{},"在同一电脑上切换 Windows 用户登录，旧用户的凭据对新用户完全不可见，因为加密密钥是用户级的。",[32,817,818],{},"多用户共享一台电脑的场景下，凭据不会跨用户暴露。",[10,820,821],{},"这些限制会在实际使用中暴露出来——比如忙于工作时重装系统忽略了导出，或者在公用工作电脑上多个人使用。正因为如此，工具强制要求提供备份机制。备份不是可选功能，而是必需的。",[22,823,825],{"id":824},"第二层备份加密的固定迭代设计","第二层：备份加密的固定迭代设计",[10,827,828],{},"备份采用 PBKDF2-SHA256（基于密码的密钥派生函数 2，使用 SHA-256 哈希）加密，固定执行 600,000 次迭代。用户创建备份时设置一个密码，导入时输入这个密码。PBKDF2 通过重复应用哈希函数来增加破解难度，迭代次数越多，从密码派生密钥所需的计算时间越长，攻击者进行暴力破解也需要投入成倍的计算资源。",[10,830,831],{},"600,000 次迭代的来源是什么？这是一个有意识的设计决定，而不是随意选择。迭代次数越多，暴力破解的成本越高，但加密和解密的耗时也越长。设计者需要在两者之间找到平衡点：够强以抵御现代硬件的破解能力，又不能强到让普通用户的导入操作变得难以忍受。600,000 次这个数字反映的是这个平衡的结果。",[10,833,834],{},"为什么固定而非可配置？这是一个纪律问题。可配置听起来更灵活、更给用户掌控权，但在实践中会诱使用户为了更快的备份导入速度而降低迭代次数，从而削弱安全强度。安全不应该由便利让步。人们往往倾向于选择快速方案，尤其当他们没有安全专业知识时。固定的迭代次数消除了这种选择权，保证了所有备份都有相同的防护等级。工具的职责是做出最合理的决定，而不是把这个决定推给用户。",[22,836,837],{"id":837},"性能实测与数据解读",[10,839,840],{},"在 Windows 11 构建机上，对 1 MiB 大小的备份数据进行加密与解密的完整往返，预热缓存后的连续五次耗时分别为：108.919 ms、104.079 ms、100.418 ms、94.933 ms、103.470 ms。中位数为 103.470 ms。这意味着从你按下\"导入备份\"到凭据被解密并加载到内存，大约需要 100 毫秒的等待时间。",[10,842,843,844,847,848,851],{},"这组数据收集的目的是",[17,845,846],{},"记录性能表现","。它提供了一个具体的参考：用户在 Windows 11 系统上可以预期导入备份的延迟大约是这个量级。这对评估工具的可用性很有用。但它明确",[17,849,850],{},"不","作为调整迭代次数的依据。这种表述听起来有些冗余，但它是设计纪律的一部分——必须写下来的目的是防止后续有人看到\"100 毫秒确实有点慢\"就建议降低迭代次数。防止的是这样的推理：因为性能数据显示延迟不够快，所以降低迭代次数。这个逻辑链条在安全工程中是禁止的。",[10,853,854],{},"相反，如果实践证明 100 毫秒对用户体验构成问题，正确的做法是要么接受这个成本作为安全性的代价，要么在未来硬件更新换代后自然加速。绝不是削弱密钥派生强度。性能和安全的权衡应该在上层的需求决策中做，而不是在密码学参数中做。",[22,856,857],{"id":857},"工具的明确边界",[10,859,860],{},"这个工具的安全设计有明确的保护范围和限制：",[10,862,863,866],{},[17,864,865],{},"DPAPI 层的限制","：本机凭据的安全性最终依赖于 Windows 用户密码。如果 Windows 账户被破解，攻击者用该账户登录电脑，DPAPI 解密会自动进行。如果用户以管理员身份运行工具（虽然不需要管理员权限），攻击者获得管理员权限后理论上也可能绕过某些保护。安全链的强度由最弱的一环决定——如果你的 Windows 用户密码很弱，或者电脑物理上被他人访问，DPAPI 的保护就名存实亡。",[10,868,869,872],{},[17,870,871],{},"备份密码的强度","：导入备份时用户设置的密码决定了备份的抗暴力破解能力。PBKDF2 提供的防护再强，也无法弥补一个简单密码的缺陷。\"123456\"这样的备份密码，在 600,000 次迭代和现代 GPU 的破解能力面前，可能在几秒到几分钟内被破解。",[10,874,875,878],{},[17,876,877],{},"系统策略的约束","：工具运行在 Windows 系统上，不会绕过任何系统级的安全机制。Windows SmartScreen 对未签名程序的警告、远程桌面连接的安全确认对话、Group Policy 的限制——这些工具都无法绕过。安装包为未签名的内部制品，Windows SmartScreen 会在首次运行时显示\"未知发布者\"警告。这不是工具的缺陷，而是系统安全策略的正常行为。",[10,880,881,884,885,888],{},[17,882,883],{},"加密设计的范围","：这套两层加密设计防的是",[17,886,887],{},"离线攻击","——攻击者获得了备份文件或本机的加密数据，在没有用户交互的情况下尝试破解。它防不了的情况：",[29,890,891,894,897,900],{},[32,892,893],{},"备份密码通过社工或偷看被直接获取",[32,895,896],{},"备份文件在网络传输过程中被中间人截获（如果使用不安全的传输方式）",[32,898,899],{},"凭据被恶意软件在内存中窃取（工具启动后、密码解密到内存这段时间内）",[32,901,902],{},"Windows 账户本身被已经登录电脑的恶意软件控制",[10,904,905],{},"对这些风险的防护需要用户在安全习惯和网络安全措施上自行补足——设置强密码、在信任的网络上操作、定期更新系统补丁、使用反恶意软件工具。",[10,907,908],{},"使用这套工具前要明确：本机凭据带来了便利，代价是将安全依赖在 Windows 用户身份上；备份凭据提供了迁移能力，代价是密码强度必须由用户自己把关。都不是\"一次设置永久安全\"的方案，都需要持续的安全意识和维护。",{"title":218,"searchDepth":447,"depth":447,"links":910},[911,912,913,914],{"id":794,"depth":447,"text":795},{"id":824,"depth":447,"text":825},{"id":837,"depth":447,"text":837},{"id":857,"depth":447,"text":857},"2026-07-20",{},"\u002F2026-07-20-dpapi60",{"title":786,"description":791},"2026-07-20-远程桌面管理器DPAPI与60万次迭代","两层加密保护远程桌面凭据，本机用 DPAPI 便利性换易用性，备份用 PBKDF2 固定 60 万迭代；为什么迭代次数不可配置，性能数据如何解读。",[922,923,924,925,926],"Windows","DPAPI","PBKDF2","凭据存储","加密设计","TzKUoZoFLtAsO_VcB1TOOdWOpwcoDj6AUwEdS6d7pv0",{"id":929,"title":930,"body":931,"column":461,"date":1050,"description":935,"extension":463,"hero_image":464,"meta":1051,"navigation":466,"path":1052,"seo":1053,"series_id":464,"severity":464,"stem":1054,"summary":1055,"tags":1056,"__hash__":1062},"posts\u002F2026-07-17-把设计文档变成可讲的演示.md","转不成演示的设计文档，本来就没讲清",{"type":7,"value":932,"toc":1044},[933,936,942,945,948,951,954,972,979,983,986,992,1003,1011,1014,1017,1020,1023,1026,1029,1032,1035,1038,1041],[10,934,935],{},"一年积累了 200 多份设计文档，最初想法是直接拿这些文档去讲。结果发现这些东西不适合讲。设计文档是给人写的，演示文稿是给人听的，形式完全不同。",[10,937,938,939,699],{},"解决这个问题的思路不是\"写个通用 PPT 编辑器\"，而是\"从结构化文档一键生成演示\"。本质差异在这里——编辑器要处理用户的任意编辑行为，演示工具只要转换",[17,940,941],{},"已有的结构",[22,943,944],{"id":944},"为什么选单向转换而不是编辑器",[10,946,947],{},"设计文档有稳定的模板：背景、方案、架构、取舍、参考。这个顺序不是随意的，恰好就是讲一个设计时的叙述顺序。",[10,949,950],{},"用户不需要\"先生成再改\"，需要的是\"文档秒变幻灯片\"。一旦你改，就回到编辑器的坑里去了——要支持拖拽、删除、排版，工作量爆炸，而且多数用户不会调，生成好的东西就是定版。",[10,952,953],{},"这个判断来自实际数据。我的工具做两个决策：",[150,955,956,966],{},[32,957,958,961,962,965],{},[17,959,960],{},"产物是自包含 HTML","，不是 Office 文件（",[105,963,964],{},".pptx"," 需要可编辑格式，门槛高；HTML 在浏览器里就能放映，自包含意味着内联了所有 CSS、JS、图片）。",[32,967,968,971],{},[17,969,970],{},"内容由 LLM 生成，不开放编辑面板","（用一个\"预览挑选\"的两阶段流程，让用户在 3 种风格的封面里选一个，然后生成整份）。",[10,973,974,975,978],{},"这两个约束听起来很严格，实际上契合了需求的本质：",[17,976,977],{},"文档的目的是记录决策，演示的目的是讲述决策","。不需要演示过程中再改决策，改了就回到文档去改。",[22,980,982],{"id":981},"什么样的结构能转什么样的不能","什么样的结构能转、什么样的不能",[10,984,985],{},"设计文档要是能一键转成演示，必须满足几个条件。",[10,987,988,991],{},[17,989,990],{},"架构图可以直接映射","。我的演示工具支持 AI 生图，也支持用户指定图片 URL。当文档里写了架构设计时，LLM 看到这个描述会生成对应的图，然后内联到演示文稿里。舞台是固定的 1920×1080，所有图片容器有最小尺寸约束（GSAP 时间轴动画要能在任意时间点求值，不能靠动态布局）。",[10,993,994,997,998,1002],{},[17,995,996],{},"关键数据用表格记录最不容易犯错","。表格这种结构容易转化——",[999,1000,1001],"span",{},"待补：具体表格如何转成演示页的实现规则","。如果文档里的数据以段落文字形式写，转成演示时就卡住了，LLM 要从自然语言反推结构。",[10,1004,1005,378,1008],{},[17,1006,1007],{},"取舍（trade-off）部分",[999,1009,1010],{},"待补：设计文档中的取舍说明如何映射成演示内容的规则",[10,1012,1013],{},"一个更深的观察：文档转不出来好演示，通常不是演示工具的问题，是文档本身没讲清楚。",[10,1015,1016],{},"我见过一类项目文档，有 20 多份模板文件，但只要换个配色其他全一样。这说明什么？说明那些\"模板\"实际上没有结构差异，只有视觉差异。结构不清，自然转不出演示。或者反过来说，如果要证明自己的文档结构是清晰的，试试能不能一键转成演示——转不出来就是信号，说明需要先梳理文档本身。",[22,1018,1019],{"id":1019},"工具的硬约束",[10,1021,1022],{},"演示不同于其他产物，有独特的硬约束。我的工具选择了 1920×1080 的固定舞台，所有动画用 GSAP 时间轴。这些看起来像限制，实际上是为了保证可靠性。",[10,1024,1025],{},"固定舞台尺寸意味着设计者写风格预设时要算好留白和排版，不能寄希望于\"容器自适应就行了\"。GSAP 时间轴的特点是能在任意时间点求值（渲染管线会 seek 到任意帧截图），不能用 CSS animation 这种依赖真实时间流逝的东西。这限制了动效，但换来的是确定性——动效一定会在预期时间点发生，不会因为网络卡而错位。",[10,1027,1028],{},"生成流程分两个阶段，有个细节很实用。第一阶段只生成 3 个风格的封面单页，第二阶段才生成整份演示文稿。这个设计看似多一步，实际上优雅地解决了两个问题。一是给用户选择风格的机会（不是非此即彼的\"生成或不生成\"）；二是掩盖异步生图的等待时间（生图 1-2 分钟，正好被用户在选择封面时吸收了）。",[22,1030,1031],{"id":1031},"从文档能否转演示看结构清晰度",[10,1033,1034],{},"最后回到起点。为什么要做这个工具？",[10,1036,1037],{},"表面原因是 200 多份文档用演讲方式讲会更有力。深层原因是这个过程本身就是对文档质量的检验。",[10,1039,1040],{},"一份设计文档如果结构清晰——背景交代得清，方案对比得充分，架构图画得明确，取舍理由说得透彻——那转成演示就是平移内容，不费劲。转不出来、或者转出来很别扭，就是信号，说明某个环节讲得不够好。",[10,1042,1043],{},"这个反馈机制比任何 review 注释都直白。\"这个表述为什么转不成幻灯片？\"往往能逼出真实的问题——\"哦，因为我其实还没想清楚这个方案为什么比另一个好\"。",{"title":218,"searchDepth":447,"depth":447,"links":1045},[1046,1047,1048,1049],{"id":944,"depth":447,"text":944},{"id":981,"depth":447,"text":982},{"id":1019,"depth":447,"text":1019},{"id":1031,"depth":447,"text":1031},"2026-07-17",{},"\u002F2026-07-17",{"title":930,"description":935},"2026-07-17-把设计文档变成可讲的演示","不做通用PPT编辑器，只做文档→演示的单向转换；关键在识别文档的稳定结构。",[1057,1058,1059,1060,1061],"演示文稿","文档结构","设计工具","HTML","GSAP","Reh87_TKXZ8nl5BhDhHzVOONAzwRS-Im8LTYrIfeuNk",1785406912235]