[{"data":1,"prerenderedAt":1274},["ShallowReactive",2],{"\u002F2026-05-18-sshtswindows":3,"\u002F2026-05-18-sshtswindows-rel":688},{"id":4,"title":5,"body":6,"column":671,"date":672,"description":12,"extension":673,"hero_image":674,"meta":675,"navigation":676,"path":677,"seo":678,"series_id":674,"severity":674,"stem":679,"summary":680,"tags":681,"__hash__":687},"posts\u002F2026-05-18-SSHTS远程调试客户Windows.md","不装远控软件，从 Mac 进客户的 Windows",{"type":7,"value":8,"toc":660},"minimark",[9,13,18,26,36,39,84,88,96,103,115,118,122,125,142,145,160,172,176,183,189,192,195,199,202,205,227,230,245,248,263,266,277,280,346,355,359,366,392,395,402,409,413,419,454,460,518,521,527,544,547,555,558,564,585,604,611,614,650,653,656],[10,11,12],"p",{},"客户机在 Windows、开发机在 Mac，现场排障需要上机。RDP 和商业远控工具都太重——要客户配合打开、有安装门槛、按并发收费。我用 SSH 隧道解决这个问题，核心思路是客户运行一个通用 BAT（自动获取连接码），通过 VPS 反向隧道连我的 Mac，然后我用 ProxyJump 直连到他的 PowerShell。这套工具支持 10 个客户并发，用一次性 ed25519 证书做身份认证，OpenSSH 缓存把首次安装时间从 30-60 秒压到 5-10 秒。",[14,15,17],"h2",{"id":16},"架构vps-中继-反向隧道-proxyjump","架构：VPS 中继 + 反向隧道 + ProxyJump",[10,19,20,21,25],{},"整个系统分三端。客户 Windows 上运行 BAT，通过 SSH 建立反向隧道到 VPS（隧道用户只有端口转发权限，无 shell 访问）；VPS 负责分配连接码和证书，维护实时会话面板；我的 Mac 上配置好 SSH 别名，一条命令 ",[22,23,24],"code",{},"ssh cust-\u003Ccode>"," 就能跳转到客户机。",[27,28,33],"pre",{"className":29,"code":31,"language":32},[30],"language-text","┌─────────┐              ┌─────────────────────────────────┐              ┌──────────────┐\n│   Mac   │              │       VPS Relay (中转站)        │              │   Customer   │\n│         │              │                                 │              │   Windows    │\n│  ssh    │──ProxyJump──►│ Web Panel  (Node 20 + Vue 3)    │◄── HTTPS ────│  Universal   │\n│cust-XXX │              │   ↓ \u002Fapi\u002Fallocate (cert)        │              │   BAT        │\n│         │              │   ↓ \u002Fapi\u002Fsessions (kill\u002Flist)   │              │              │\n│         │              │   ↓ \u002Fws (realtime push)         │              │              │\n│         │              │ sshd  reverse port pool         │              │              │\n│         │              │   12100~12199 (cert-only auth)  │◄ ssh -R 12X ─│              │\n│         │              │ nft per-port counters → traffic │              │              │\n│         │              │ SQLite (sessions+audit+samples) │              │              │\n└─────────┘              └─────────────────────────────────┘              └──────────────┘\n","text",[22,34,31],{"__ignoreMap":35},"",[10,37,38],{},"流程是这样的：",[40,41,42,49,55,58,64,70,73,78,81],"ol",{},[43,44,45,46],"li",{},"客户双击 ",[22,47,48],{},"远程调试-连接.bat",[43,50,51,52],{},"BAT 生成一次性 SSH 密钥对，调用 ",[22,53,54],{},"POST https:\u002F\u002F中转站\u002Fapi\u002Fallocate",[43,56,57],{},"服务器返回三位连接码（例如 147）、对应端口（12147）、签发的证书、工程师公钥、VPS 主机公钥",[43,59,60,61],{},"BAT 自动安装或启动 OpenSSH Server，把工程师公钥写入 ",[22,62,63],{},"authorized_keys",[43,65,66,67],{},"BAT 用证书建立反向隧道：",[22,68,69],{},"ssh -i key -o CertificateFile=cert -R 12147:127.0.0.1:22 tunnel@中转站",[43,71,72],{},"BAT 显示连接码给客户（\"您的连接码: 147\"）",[43,74,75,76],{},"客户告诉支持这个数字，我在 Mac 上运行 ",[22,77,24],{},[43,79,80],{},"连接实时出现在 Web 面板上（代码、IP、流量、时长）",[43,82,83],{},"客户关闭 BAT 窗口或我在面板点击\"关闭\"，隧道断开",[14,85,87],{"id":86},"设计点一动态连接码-端口池支持-10-并发","设计点一：动态连接码 + 端口池，支持 10 并发",[10,89,90,91,95],{},"三位数连接码（100",[92,93,94],"del",{},"199）是为了让客户容易记住和口头传达。服务器维护一个端口池 12100","12199，每次客户请求时随机分配，确保 10 个客户可以并发连接而不冲突。",[10,97,98,99,102],{},"请求 ",[22,100,101],{},"\u002Fapi\u002Fallocate"," 时，服务器：",[104,105,106,109,112],"ul",{},[43,107,108],{},"检查当前空闲端口",[43,110,111],{},"返回 code（100~199 之间随机）和对应的 port（12100+code）",[43,113,114],{},"这个分配关系存进 SQLite，关联客户 IP、时间戳、隧道建立状态",[10,116,117],{},"如果 10 个端口都满，新客户会得到 HTTP 503 错误和\"pool_full\"错误码，这是硬限制的立即拒绝。",[14,119,121],{"id":120},"设计点二一次性-ed25519-证书5-分钟有效且绑定源-ip","设计点二：一次性 ed25519 证书，5 分钟有效且绑定源 IP",[10,123,124],{},"传统做法是给每个客户一个长期 SSH 密钥，密钥泄露风险大。这里改成 OpenSSH 证书认证：",[104,126,127,130,136,139],{},[43,128,129],{},"客户 BAT 生成临时 ed25519 密钥对（仅在 BAT 运行时内存中存在）",[43,131,132,133,135],{},"客户把公钥和源 IP 发给服务器的 ",[22,134,101],{}," 接口",[43,137,138],{},"服务器签发一张证书：标明这个公钥只在这次连接有效、5 分钟过期、源 IP 绑定",[43,140,141],{},"BAT 用证书建立隧道",[10,143,144],{},"OpenSSH 证书的威胁模型是：",[104,146,147,154],{},[43,148,149,153],{},[150,151,152],"strong",{},"能保护的","：密钥长期泄露风险消除了（每次用完即弃）；隧道被劫持后，拦截者只能在 5 分钟内、同一源 IP 范围内冒充这个客户，其他时间段无法复用",[43,155,156,159],{},[150,157,158],{},"不能保护的","：如果源 IP 被欺骗（ARP 欺骗、BGP 劫持）、或 VPS 时钟不准（偏差超过 5 分钟），证书校验可能失败或被绕过；BAT 文件本身被篡改（在传输或客户机上被修改），工程师公钥被替换，我连接时会连到恶意机器而不是客户机",[10,161,162,163,166,167,171],{},"证书的有效期使用 OpenSSH ",[22,164,165],{},"ssh-keygen -V +300s"," 格式，依赖客户机和服务器的系统时钟一致。如果两端时钟相差超过几分钟，OpenSSH 会因时间戳不在有效范围内而拒绝证书。系统没有显式的时钟容差机制，NTP 同步精度需要控制在分钟级以内，IPv6 或 VPN 环境下源 IP 绑定的有效性 ",[168,169,170],"span",{},"待补：取决于 VPN 方案是否保留客户真实 IP","。",[14,173,175],{"id":174},"设计点三隧道账户仅端口转发无-shell-权限","设计点三：隧道账户仅端口转发，无 shell 权限",[10,177,178,179,182],{},"VPS 上创建一个专用账户 ",[22,180,181],{},"tunnel","，SSH 配置中限制它：",[27,184,187],{"className":185,"code":186,"language":32},[30],"# sshd_config 关键配置\nMatch User tunnel\n  AllowAgentForwarding no\n  AllowTcpForwarding yes\n  PermitListen 127.0.0.1:12100-12199\n  PermitOpen 127.0.0.1:12100-12199\n  PermitTTY no\n  AllowUsers tunnel\n  ForceCommand \u002Fbin\u002Ffalse\n",[22,188,186],{"__ignoreMap":35},[10,190,191],{},"这样即使客户的密钥（或我的长期密钥）泄露，攻击者登陆 tunnel 账户最多只能建立反向端口转映射，无法获得 shell、无法读写文件系统、无法执行命令。双重保护：一是账户本身没有 shell，二是 sshd 配置显式禁止了 TTY 和代理转发。",[10,193,194],{},"这里的威胁模型假设：VPS 的 sshd 配置被信任，未被篡改；客户 Windows 机器上没有被植入远控木马（BAT 文件下载、执行、隧道运行的全过程都可能被监控）。如果客户机本身被入侵，这套机制无法保护 VPS 或我的 Mac。",[14,196,198],{"id":197},"设计点四openssh-预缓存5-10-秒装好而不是-30-60-秒","设计点四：OpenSSH 预缓存，5-10 秒装好而不是 30-60 秒",[10,200,201],{},"Windows 内置的 OpenSSH Server 需要从 Windows Update 下载，通常要 30-60 秒。方案是在 VPS 上预先缓存一份 OpenSSH-Win64.zip（~50MB），BAT 运行时优先从本地 VPS 的 HTTP 服务（18022 端口）下载，失败才降级到 Windows Update。",[10,203,204],{},"VPS 侧的一次性准备：",[27,206,210],{"className":207,"code":208,"language":209,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark","sudo bash server\u002Fprepare-openssh-cache.sh\n","bash",[22,211,212],{"__ignoreMap":35},[168,213,216,220,224],{"class":214,"line":215},"line",1,[168,217,219],{"class":218},"sScJk","sudo",[168,221,223],{"class":222},"sZZnC"," bash",[168,225,226],{"class":222}," server\u002Fprepare-openssh-cache.sh\n",[10,228,229],{},"这个脚本：",[104,231,232,235,238],{},[43,233,234],{},"从 GitHub PowerShell\u002FWin32-OpenSSH releases 官方源下载 OpenSSH-Win64.zip",[43,236,237],{},"启动 HTTP 服务在 18022 端口，提供这个 ZIP",[43,239,240,241,244],{},"不做显式的哈希校验，而是依赖 Windows 对 ZIP 文件内签名的验证（Windows 的 ",[22,242,243],{},"Add-WindowsCapability"," 内置验证机制）",[10,246,247],{},"客户 BAT 的逻辑：",[104,249,250,257,260],{},[43,251,252,253,256],{},"尝试从 VPS ",[22,254,255],{},"http:\u002F\u002F中转站:18022\u002FOpenSSH-Win64.zip"," 下载（网络好时 5-10 秒）",[43,258,259],{},"下载失败或超时则降级到 Windows Update（30-60 秒）",[43,261,262],{},"无论哪种方式都自动继续，用户不需要干预",[10,264,265],{},"这个优化需要几个前提条件生效：",[104,267,268,271,274],{},[43,269,270],{},"VPS 和客户机的网络连接要稳定（下载中断会回源 Windows Update，反而更慢）",[43,272,273],{},"VPS 和客户机在同一地域最佳（跨域 50MB 可能需要 20-30 秒，降级意义不大）",[43,275,276],{},"HTTP 18022 端口要对客户机开放（如果防火墙限制只有 22\u002F443，这个缓存无法使用）",[10,278,279],{},"素材里给了性能表：",[281,282,283,302],"table",{},[284,285,286],"thead",{},[287,288,289,293,296,299],"tr",{},[290,291,292],"th",{},"场景",[290,294,295],{},"时间 (v1.0)",[290,297,298],{},"时间 (v1.1)",[290,300,301],{},"加速",[303,304,305,320,333],"tbody",{},[287,306,307,311,314,317],{},[308,309,310],"td",{},"首次安装（网络好）",[308,312,313],{},"30-60s",[308,315,316],{},"5-10s",[308,318,319],{},"3-10x",[287,321,322,325,328,330],{},[308,323,324],{},"首次安装（网络慢）",[308,326,327],{},"2-3 min",[308,329,313],{},[308,331,332],{},"2-5x",[287,334,335,338,341,343],{},[308,336,337],{},"已安装",[308,339,340],{},"0.5s",[308,342,340],{},[308,344,345],{},"相同",[10,347,348,354],{},[150,349,350,351],{},"测量条件 ",[168,352,353],{},"待补：网络\"好\"和\"慢\"的具体定义、客户端机型、测试样本数量","。现有数据支撑的结论是：新客户首次连接，这个优化能缩短 60-75% 的等待时间（从 60s 到 10s 这个最好情况）；但不能保证 100% 稳定达成（取决于网络波动）。",[14,356,358],{"id":357},"web-管理面板实时掌握会话状态","Web 管理面板：实时掌握会话状态",[10,360,361,362,365],{},"VPS 上跑的 Node 20 + Vue 3 前端，可以访问 ",[22,363,364],{},"https:\u002F\u002F中转站","（配置 Let's Encrypt 证书）。面板功能：",[104,367,368,374,380,386],{},[43,369,370,373],{},[150,371,372],{},"实时会话列表","：显示当前在线客户的代码、IP、连接时长、流量统计",[43,375,376,379],{},[150,377,378],{},"强制断开","：点击\"关闭\"按钮立即切断隧道",[43,381,382,385],{},[150,383,384],{},"历史记录","：已结束的所有会话，记录开始\u002F结束时间、流量、断开原因（主动关闭 vs 网络中断 vs 空闲超时）",[43,387,388,391],{},[150,389,390],{},"审计日志","：Web 面板登陆尝试、谁强制断开了谁、异常操作",[10,393,394],{},"数据存储在 VPS 的 SQLite 里，包括 sessions 表（会话元数据：代码、端口、客户 IP、开始\u002F结束时间、流量总计）、audit 表（操作日志）、traffic_samples 表（按时间采样的流量数据：会话 ID、采样时刻、接收字节、发送字节）。",[10,396,397,398,401],{},"流量统计通过 ",[22,399,400],{},"nft","（Linux 内核防火墙规则）的计数器实现，每个端口 12100~12199 对应一个计数规则，实时汇总给 Web 后端。",[10,403,404,405,408],{},"登陆认证是用户名 + 密码（在 ",[22,406,407],{},"install-panel.sh"," 时设置）。",[14,410,412],{"id":411},"部署4-个步骤完成全套安装","部署：4 个步骤完成全套安装",[10,414,415,418],{},[150,416,417],{},"第一步","：生成隧道密钥（Mac 侧）",[27,420,422],{"className":207,"code":421,"language":209,"meta":35,"style":35},"ssh-keygen -t ed25519 -f keys\u002Fcustomer_tunnel_key -N \"\" -C \"customer-tunnel\"\n",[22,423,424],{"__ignoreMap":35},[168,425,426,429,433,436,439,442,445,448,451],{"class":214,"line":215},[168,427,428],{"class":218},"ssh-keygen",[168,430,432],{"class":431},"sj4cs"," -t",[168,434,435],{"class":222}," ed25519",[168,437,438],{"class":431}," -f",[168,440,441],{"class":222}," keys\u002Fcustomer_tunnel_key",[168,443,444],{"class":431}," -N",[168,446,447],{"class":222}," \"\"",[168,449,450],{"class":431}," -C",[168,452,453],{"class":222}," \"customer-tunnel\"\n",[10,455,456,459],{},[150,457,458],{},"第二步","：配置 VPS 隧道和 sshd",[27,461,463],{"className":207,"code":462,"language":209,"meta":35,"style":35},"ssh root@中转站\ngit clone \u003C仓库地址>\ncd sshts\nsudo bash server\u002Fsetup-vps.sh keys\u002Fcustomer_tunnel_key.pub\n",[22,464,465,473,496,505],{"__ignoreMap":35},[168,466,467,470],{"class":214,"line":215},[168,468,469],{"class":218},"ssh",[168,471,472],{"class":222}," root@中转站\n",[168,474,476,479,482,486,489,493],{"class":214,"line":475},2,[168,477,478],{"class":218},"git",[168,480,481],{"class":222}," clone",[168,483,485],{"class":484},"szBVR"," \u003C",[168,487,488],{"class":222},"仓库地",[168,490,492],{"class":491},"sVt8B","址",[168,494,495],{"class":484},">\n",[168,497,499,502],{"class":214,"line":498},3,[168,500,501],{"class":431},"cd",[168,503,504],{"class":222}," sshts\n",[168,506,508,510,512,515],{"class":214,"line":507},4,[168,509,219],{"class":218},[168,511,223],{"class":222},[168,513,514],{"class":222}," server\u002Fsetup-vps.sh",[168,516,517],{"class":222}," keys\u002Fcustomer_tunnel_key.pub\n",[10,519,520],{},"这会创建 tunnel 账户、配置 sshd、启用反向隧道、写入 authorized_keys。",[10,522,523,526],{},[150,524,525],{},"第三步","：安装 Web 面板（v1.2+）",[27,528,530],{"className":207,"code":529,"language":209,"meta":35,"style":35},"sudo DOMAIN=中转站 bash server\u002Finstall-panel.sh\n",[22,531,532],{"__ignoreMap":35},[168,533,534,536,539,541],{"class":214,"line":215},[168,535,219],{"class":218},[168,537,538],{"class":222}," DOMAIN=中转站",[168,540,223],{"class":222},[168,542,543],{"class":222}," server\u002Finstall-panel.sh\n",[10,545,546],{},"脚本会提示输入：",[104,548,549,552],{},[43,550,551],{},"工程师 SSH 公钥（会被嵌入所有 BAT）",[43,553,554],{},"Web 面板管理员用户名和密码",[10,556,557],{},"然后自动安装 Node 20、编译 Vue 3 前端、配置 systemd 服务、Nginx + Let's Encrypt。",[10,559,560,563],{},[150,561,562],{},"第四步","：配置 Mac 的 SSH 别名（每个工程师一次）",[27,565,567],{"className":207,"code":566,"language":209,"meta":35,"style":35},"WIN_USER=Administrator bash mac\u002Finstall-ssh-config.sh\n",[22,568,569],{"__ignoreMap":35},[168,570,571,574,577,580,582],{"class":214,"line":215},[168,572,573],{"class":491},"WIN_USER",[168,575,576],{"class":484},"=",[168,578,579],{"class":222},"Administrator",[168,581,223],{"class":218},[168,583,584],{"class":222}," mac\u002Finstall-ssh-config.sh\n",[10,586,587,588,591,592,595,596,599,600,603],{},"这会在 ",[22,589,590],{},"~\u002F.ssh\u002Fconfig"," 里添加 100 条记录：",[22,593,594],{},"cust-100"," 到 ",[22,597,598],{},"cust-199","，每个都用 ",[22,601,602],{},"ProxyJump=中转站","，自动登陆指定的 Windows 管理员。",[10,605,606,607,610],{},"之后，把 ",[22,608,609],{},"client\u002F远程调试-连接.bat"," 发给客户（通用的，不需要填任何参数），客户一运行就能连接。",[14,612,613],{"id":613},"限制和取舍",[104,615,616,626,632,638,644],{},[43,617,618,621,622,625],{},[150,619,620],{},"支持 10 并发","：这是硬限制，对应 VPS 单机性能（",[168,623,624],{},"待补：性能上限如何测试、是否会因为某个客户的高流量拖累其他客户",")。",[43,627,628,631],{},[150,629,630],{},"仅支持 Windows 10 1809+ 或 Windows 11","：早期 Windows 版本的 OpenSSH 行为不兼容，素材里明确要求 Administrator 账户。",[43,633,634,637],{},[150,635,636],{},"需要 Administrator 权限","：BAT 要写 SSH 密钥到系统目录、启动 sshd 服务，非 Admin 用户无法运行。BAT 会自动检测管理员账户，优先当前用户，降级到 Administrator（需要 UAC 弹窗确认）。",[43,639,640,643],{},[150,641,642],{},"证书有效期 5 分钟","：这是为了限制泄露风险，代价是客户机上的系统时钟如果跟服务器相差超过几分钟，连接会失败。系统依赖两端时钟同步，无显式容差机制。",[43,645,646,649],{},[150,647,648],{},"Web 面板仅支持单一 admin 账户","：系统只有一个管理员用户，通过用户名+密码认证。所有认证用户都拥有完整权限（查看所有会话、断开任何连接、查看审计日志），不支持按角色分配权限或只读账户。",[14,651,652],{"id":652},"关键点",[10,654,655],{},"这套工具解决的是客户排障的第一步——快速、轻量地建立远程访问，而不需要客户安装 TeamViewer、配置 RDP、开放额外端口。支持 10 并发、一次性证书，OpenSSH 缓存把安装时间从 30-60 秒压到 5-10 秒，意味着哪怕客户现场网络不稳定、客户 IT 管理严格，也能以最小摩擦快速上机取证。没有这套工具，那些现场的故障根本无法追踪——光是等待客户开 RDP 就要 10 分钟，OpenSSH 缓存的优化才让 5-10 秒的快速连接成为现实。",[657,658,659],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":35,"searchDepth":475,"depth":475,"links":661},[662,663,664,665,666,667,668,669,670],{"id":16,"depth":475,"text":17},{"id":86,"depth":475,"text":87},{"id":120,"depth":475,"text":121},{"id":174,"depth":475,"text":175},{"id":197,"depth":475,"text":198},{"id":357,"depth":475,"text":358},{"id":411,"depth":475,"text":412},{"id":613,"depth":475,"text":613},{"id":652,"depth":475,"text":652},"工程手记","2026-05-18","md",null,{},true,"\u002F2026-05-18-sshtswindows",{"title":5,"description":12},"2026-05-18-SSHTS远程调试客户Windows","通过 SSH 隧道 + VPS 中继接客户 Windows，支持 10 并发、一次性证书、5-10 秒快速连接。",[682,683,684,685,686],"SSH隧道","reverse-tunnel","ProxyJump","ed25519证书","远程调试","ywgG6vps7Ox2LEtWq36_6F-8ee3rIF7WT2dtbCJjpQE",[689,995,1139],{"id":690,"title":691,"body":692,"column":671,"date":982,"description":696,"extension":673,"hero_image":674,"meta":983,"navigation":676,"path":984,"seo":985,"series_id":674,"severity":674,"stem":986,"summary":987,"tags":988,"__hash__":994},"posts\u002F2026-07-29-一年八千次提交AI辅助开发工作流.md","一年八千次提交，我是怎么干的",{"type":7,"value":693,"toc":965},[694,697,700,703,708,711,714,717,721,724,727,741,744,748,751,754,757,761,764,767,787,790,794,797,808,811,815,818,832,835,838,842,845,848,852,855,858,861,865,868,871,895,902,905,911,914,917,920,927,930,933,959,962],[10,695,696],{},"2026 上半年，主要项目累计提交 7787 次（其中 newCodex 因为是 fork 项目，1922 次提交含上游历史；纯新增代码的项目是 yun-claude、yun-claw、new-openclaw 等）。提交数看起来多，但每个提交背后的价值不在数量，而在流程的可重复性。这篇文章把工作流写清楚。",[14,698,699],{"id":699},"流程的六个环节",[10,701,702],{},"整个开发周期分六步：需求澄清 → 设计文档审阅 → 拆实施计划 → 分阶段实现 → 代码审查 → 故障归档。每一步都有明确的输入输出和验证方式。",[704,705,707],"h3",{"id":706},"_1-需求澄清","1. 需求澄清",[10,709,710],{},"开始前问清楚，不要猜。典型问题：这个功能要处理哪些用户场景？边界条件是什么？现有系统的哪部分会受影响？",[10,712,713],{},"这一步的产出是一份结构化的需求文档，包括功能范围、约束条件、风险假设。不是长篇幅的铺垫，而是一份清单式的澄清记录。",[10,715,716],{},"在 new-openclaw 项目里，这一步通常是用 Claude 的 superpowers:brainstorming skill 来展开。提问方式很重要：我会列出已知条件，然后问\"这个设计下会遇到什么问题？\"而不是\"你觉得应该怎么做？\"让 AI 在我的约束框架内工作。",[704,718,720],{"id":719},"_2-设计文档与人工审阅","2. 设计文档与人工审阅",[10,722,723],{},"这是整个流程里最关键的检查点。花一小时审一份 200 行的设计文档，比花五小时审一份 2000 行的代码便宜得多。而且回头修改设计的成本远低于修改实现。",[10,725,726],{},"new-openclaw 项目现有 30 份设计文档（specs 目录），每份文档都包括：",[104,728,729,732,735,738],{},[43,730,731],{},"范围：这个设计覆盖什么、不覆盖什么",[43,733,734],{},"决策与权衡：为什么选这个方案，放弃了什么",[43,736,737],{},"接口契约：如果涉及多个模块，清晰定义每个边界",[43,739,740],{},"风险清单：已知的坑和防护措施",[10,742,743],{},"写完设计文档以后，我会读一遍、问几个\"为什么\"，然后提出修改意见。这一步排除了 80% 的方向错误。常见的修改方向有：缩小范围（第一个版本不用处理那么多边界情况）、明确约束（系统资源、网络延迟、并发数的假设）、补充防护（熔断、限流、幂等）。",[704,745,747],{"id":746},"_3-实施计划与-dod-定义","3. 实施计划与 DoD 定义",[10,749,750],{},"设计文档定下来以后，拆成实施计划。计划的粒度是\"一个可验证的功能单元\"——通常是一个小时到半天的工作量，完成后能单独验证成功。",[10,752,753],{},"计划文档里必须写清完成定义（DoD，Definition of Done）。不是\"实现登录功能\"，而是\"写出能拒绝无效格式的登录端点、覆盖单点故障下的重试、有端到端的冒烟测试\"。",[10,755,756],{},"new-openclaw 项目现有 36 份实施计划（plans 目录），跨度从一周的 S0 阶段（骨架 + Mock 后台）到数周的 S1 阶段（真实业务后台）。每份计划都带着清晰的 checklist，这样我在执行时能随时问 Claude：\"下一步应该是什么？\"而不是脑子里模糊地记着进度。",[704,758,760],{"id":759},"_4-分阶段实现与逐步验证","4. 分阶段实现与逐步验证",[10,762,763],{},"有了计划以后，按顺序实现。关键是每一步都要有验证：单元测试、集成测试、或者一个小的 end-to-end 冒烟测试。验证不通过就停在这一步，不往下推。",[10,765,766],{},"这一步会用到三个代理：",[104,768,769,775,781],{},[43,770,771,774],{},[150,772,773],{},"superpowers:test-driven-development"," —— 先写测试，再写实现",[43,776,777,780],{},[150,778,779],{},"superpowers:subagent-driven-development"," —— 复杂任务拆成独立的子任务，并行推进",[43,782,783,786],{},[150,784,785],{},"superpowers:systematic-debugging"," —— 遇到测试失败，用这个代理追根溯源，不要盲目修改代码",[10,788,789],{},"实施过程中如果发现设计假设错了（比如某个接口响应时间远超预期，或者并发场景下出现竞态条件），就停下来回到第 2 步重新审视设计，而不是继续往下推。",[704,791,793],{"id":792},"_5-代码审查","5. 代码审查",[10,795,796],{},"实现完成以后，不是立即合并，而是过一遍 code-reviewer 代理。审查的重点不在代码风格（那个自动工具做），而在：",[104,798,799,802,805],{},[43,800,801],{},"这段代码实现的是设计文档里的哪一部分？偏离了吗？",[43,803,804],{},"错误处理有没有遗漏？边界情况有没有考虑？",[43,806,807],{},"有没有意外改动无关的代码？",[10,809,810],{},"审查通常能抓住两类问题。一类是逻辑问题：某个条件判断漏了一个分支，或者并发场景下两个操作的顺序反了。另一类是\"设计和实现对不上\"：实现了一个设计里没提到的特性，或者某个约束（比如\"这个值不能为空\"）没在代码里强制。",[704,812,814],{"id":813},"_6-故障归档","6. 故障归档",[10,816,817],{},"系统上线以后，bug 是难免的。重要的是怎么处理它。new-openclaw 项目有一套 bug 知识库规范（CLAUDE.md 里定义），每个 bug 修好以后都要新建一份独立文档，包括：",[104,819,820,823,826,829],{},[43,821,822],{},"现象和复现路径",[43,824,825],{},"根因分析：为什么会发生，触发链路是什么",[43,827,828],{},"修复方法：改了什么，为什么选这个方案",[43,830,831],{},"预防措施：代码改进、测试添加、还是构建期检查",[10,833,834],{},"80 份 bug 文档（截至 5 月中旬）不是问题的多，而是追根溯源的记录的多。下次遇到类似现象，能直接查库而不是重新排查。",[14,836,837],{"id":837},"三条铁律",[704,839,841],{"id":840},"_1-假设必须显式声明","1. 假设必须显式声明",[10,843,844],{},"不要猜。不清楚的地方就问，把问题写成澄清清单。\"这个 API 能处理多大的请求体？\"、\"离线场景下要缓存多长时间？\"、\"错误重试间隔是指数退避还是固定时间？\"。",[10,846,847],{},"这些问题看起来小，但决定了实现的复杂度和测试用例的多少。猜错了会导致前期设计精美，但方向错误，后面要推倒重来。",[704,849,851],{"id":850},"_2-修改必须精确","2. 修改必须精确",[10,853,854],{},"只改需要改的部分。这听起来像常识，但在实际工作中容易出现\"顺手改一下边上的代码\"的情况 —— 格式不规范了，变量命名不一致了，某个函数太长了，\"顺便\"重构一下。",[10,856,857],{},"结果是一个改动影响了五个文件，代码审查花了双倍时间，引入了新 bug 的风险。",[10,859,860],{},"规则是：改动必须对应需求的某一行。格式、风格、无关的重构，单独立项，不要混在功能改动里。",[704,862,864],{"id":863},"_3-成功标准必须可验证","3. 成功标准必须可验证",[10,866,867],{},"\"添加验证\"这个说法是模糊的。改写成：\"写出一个测试用例，输入非法邮箱格式，验证 API 返回 400；输入合法邮箱，验证返回 200 和预期数据结构\"。",[10,869,870],{},"每个 plan 文档里的 DoD 都是这样写的。拿一个阶段做例子：",[872,873,874,877],"blockquote",{},[10,875,876],{},"S0 阶段的 DoD：",[40,878,879,882,889,892],{},[43,880,881],{},"openapi\u002Fapi-v1.yaml 包含 spec 全部 11 个端点的字段级 schema，可被 swagger-ui 加载 ✓",[43,883,884,885,888],{},"backend\u002F Mock 服务 ",[22,886,887],{},"npm start"," 后能响应全部端点，返回符合契约的 mock 数据 ✓",[43,890,891],{},"launcher\u002F Rust 项目能编译为 launcher.exe，运行后完成所有阶段扫描并输出 diagnostics.json ✓",[43,893,894],{},"至少一个 end-to-end 冒烟测试通过（launcher 上报 → mock 后台收到 → 审计日志记录） ✓",[10,896,897,898,901],{},"每一条都能通过一个具体的命令来验证。\"能工作\"太模糊，\"运行 ",[22,899,900],{},"npm test"," 且所有测试通过\"才是可验证的。",[14,903,904],{"id":904},"流程失效的情况",[10,906,907,908,171],{},"这套流程在一个关键点会失效：",[150,909,910],{},"需求本身没想清楚时",[10,912,913],{},"我遇到过的例子是这样的。客户说\"要支持 USB 存储检测\"，这个需求很清楚，所以设计文档写了 20 多页，规划了三个阶段，列出了 30 多个 test case。然后两周后，客户补充说\"哦对了，还要处理网络驱动器\"。",[10,915,916],{},"这时前面的设计和计划都要回头改。检测逻辑复杂了，测试场景翻倍，阶段划分要调整。这不是流程的问题，这是需求的问题。流程本身反而帮助我及时暴露了这个风险 —— 如果没有设计文档，可能要到代码审查阶段，甚至系统上线以后才发现这个遗漏。",[10,918,919],{},"应对办法是在第 1 步（需求澄清）多花时间。列出你想到的所有场景，问\"还有其他我忽略的情况吗？\"。不是要求完全预测未来，而是把已知的不确定性显式写出来，而不是假设需求是固定的。",[10,921,922,923,926],{},"另一个失效的情况是",[150,924,925],{},"设计和实现的沟通不畅","。如果设计文档是给另一个人读的（或者给 AI 代理读的），但执行者没有理解透彻，实现出来会偏离设计。预防办法是在开始实施前，再过一遍设计文档，确认\"我清楚要做什么\"。",[14,928,929],{"id":929},"提交数字背后的故事",[10,931,932],{},"为什么能积累 7787 次提交？不是因为每次都在写新功能。真实的分布大概是：",[104,934,935,941,947,953],{},[43,936,937,940],{},[150,938,939],{},"功能实现","：40%",[43,942,943,946],{},[150,944,945],{},"设计文档编写和迭代","：25%",[43,948,949,952],{},[150,950,951],{},"测试编写","：20%",[43,954,955,958],{},[150,956,957],{},"bug 修复与回归测试","：15%",[10,960,961],{},"关键是这些提交都有上下文。每个提交的 message 都指向一个设计文档或一个 plan 的某个环节，或者一个 bug 记录。下次有问题要追查根因时，能快速定位到那个提交，看当时的设计决策是什么。",[10,963,964],{},"另外，有 80 份 bug 记录和 66 份设计 + 计划文档（30 specs + 36 plans）这件事本身说明了一点：文档不是负担，文档是工作的实际产出。代码只是文档的一个落地形式。",{"title":35,"searchDepth":475,"depth":475,"links":966},[967,975,980,981],{"id":699,"depth":475,"text":699,"children":968},[969,970,971,972,973,974],{"id":706,"depth":498,"text":707},{"id":719,"depth":498,"text":720},{"id":746,"depth":498,"text":747},{"id":759,"depth":498,"text":760},{"id":792,"depth":498,"text":793},{"id":813,"depth":498,"text":814},{"id":837,"depth":475,"text":837,"children":976},[977,978,979],{"id":840,"depth":498,"text":841},{"id":850,"depth":498,"text":851},{"id":863,"depth":498,"text":864},{"id":904,"depth":475,"text":904},{"id":929,"depth":475,"text":929},"2026-07-29",{},"\u002F2026-07-29-ai",{"title":691,"description":696},"2026-07-29-一年八千次提交AI辅助开发工作流","从需求澄清到故障归档，一套系统化的 AI 辅助开发流程：先设计文档过审，再分阶段实施验证，最后代码审查与故障存档，三条铁律支撑整个流程。",[989,990,991,992,993],"Claude","工作流","AI 辅助开发","代码审查","文档驱动","DN7sqbGmg2UybSvRXTMLW-dj0UURXET3KsSNeW_1V9I",{"id":996,"title":997,"body":998,"column":671,"date":1126,"description":1002,"extension":673,"hero_image":674,"meta":1127,"navigation":676,"path":1128,"seo":1129,"series_id":674,"severity":674,"stem":1130,"summary":1131,"tags":1132,"__hash__":1138},"posts\u002F2026-07-20-远程桌面管理器DPAPI与60万次迭代.md","密码，不该由我保管",{"type":7,"value":999,"toc":1120},[1000,1003,1007,1010,1013,1016,1030,1033,1037,1040,1043,1046,1049,1052,1063,1066,1069,1072,1078,1084,1090,1100,1114,1117],[10,1001,1002],{},"远程服务器资料管理工具需要妥善保存账户凭据。这个工具采用两层加密设计：本机存储用 Windows DPAPI，备份使用基于密码的密钥派生。两层各有边界，理解这些边界对使用决策至关重要。",[14,1004,1006],{"id":1005},"第一层dpapi-的便利与代价","第一层：DPAPI 的便利与代价",[10,1008,1009],{},"本机存储的凭据使用 Windows DPAPI（Data Protection API）加密，由当前 Windows 用户身份保护。这是 Windows 内置的用户级加密机制，密钥由操作系统管理，与登录用户的 SID 和机器的本地安全数据库绑定。不需要用户记一个额外的主密码，启动应用即可直接使用保存的凭据。",[10,1011,1012],{},"DPAPI 的好处显而易见：启动应用即用，无需输入密码，用户体验最优。代价是它的加密密钥锁定在当前用户、当前机器。凭据无法直接迁移到另一台电脑或另一个 Windows 用户——这不是工具的限制，而是 DPAPI 的设计约束。Windows 操作系统就是这样设计的，其他工具也无法绕过。",[10,1014,1015],{},"实际操作中的含义很明确：",[104,1017,1018,1021,1024,1027],{},[43,1019,1020],{},"重装 Windows 前必须先导出备份。原有凭据会因为用户 SID 变化和机器密钥更新而无法解密，即便登录同一账户也不行。",[43,1022,1023],{},"更换电脑前需要先创建备份并妥善保存备份密码，目标电脑上导入时需要重新输入这个备份密码。",[43,1025,1026],{},"在同一电脑上切换 Windows 用户登录，旧用户的凭据对新用户完全不可见，因为加密密钥是用户级的。",[43,1028,1029],{},"多用户共享一台电脑的场景下，凭据不会跨用户暴露。",[10,1031,1032],{},"这些限制会在实际使用中暴露出来——比如忙于工作时重装系统忽略了导出，或者在公用工作电脑上多个人使用。正因为如此，工具强制要求提供备份机制。备份不是可选功能，而是必需的。",[14,1034,1036],{"id":1035},"第二层备份加密的固定迭代设计","第二层：备份加密的固定迭代设计",[10,1038,1039],{},"备份采用 PBKDF2-SHA256（基于密码的密钥派生函数 2，使用 SHA-256 哈希）加密，固定执行 600,000 次迭代。用户创建备份时设置一个密码，导入时输入这个密码。PBKDF2 通过重复应用哈希函数来增加破解难度，迭代次数越多，从密码派生密钥所需的计算时间越长，攻击者进行暴力破解也需要投入成倍的计算资源。",[10,1041,1042],{},"600,000 次迭代的来源是什么？这是一个有意识的设计决定，而不是随意选择。迭代次数越多，暴力破解的成本越高，但加密和解密的耗时也越长。设计者需要在两者之间找到平衡点：够强以抵御现代硬件的破解能力，又不能强到让普通用户的导入操作变得难以忍受。600,000 次这个数字反映的是这个平衡的结果。",[10,1044,1045],{},"为什么固定而非可配置？这是一个纪律问题。可配置听起来更灵活、更给用户掌控权，但在实践中会诱使用户为了更快的备份导入速度而降低迭代次数，从而削弱安全强度。安全不应该由便利让步。人们往往倾向于选择快速方案，尤其当他们没有安全专业知识时。固定的迭代次数消除了这种选择权，保证了所有备份都有相同的防护等级。工具的职责是做出最合理的决定，而不是把这个决定推给用户。",[14,1047,1048],{"id":1048},"性能实测与数据解读",[10,1050,1051],{},"在 Windows 11 构建机上，对 1 MiB 大小的备份数据进行加密与解密的完整往返，预热缓存后的连续五次耗时分别为：108.919 ms、104.079 ms、100.418 ms、94.933 ms、103.470 ms。中位数为 103.470 ms。这意味着从你按下\"导入备份\"到凭据被解密并加载到内存，大约需要 100 毫秒的等待时间。",[10,1053,1054,1055,1058,1059,1062],{},"这组数据收集的目的是",[150,1056,1057],{},"记录性能表现","。它提供了一个具体的参考：用户在 Windows 11 系统上可以预期导入备份的延迟大约是这个量级。这对评估工具的可用性很有用。但它明确",[150,1060,1061],{},"不","作为调整迭代次数的依据。这种表述听起来有些冗余，但它是设计纪律的一部分——必须写下来的目的是防止后续有人看到\"100 毫秒确实有点慢\"就建议降低迭代次数。防止的是这样的推理：因为性能数据显示延迟不够快，所以降低迭代次数。这个逻辑链条在安全工程中是禁止的。",[10,1064,1065],{},"相反，如果实践证明 100 毫秒对用户体验构成问题，正确的做法是要么接受这个成本作为安全性的代价，要么在未来硬件更新换代后自然加速。绝不是削弱密钥派生强度。性能和安全的权衡应该在上层的需求决策中做，而不是在密码学参数中做。",[14,1067,1068],{"id":1068},"工具的明确边界",[10,1070,1071],{},"这个工具的安全设计有明确的保护范围和限制：",[10,1073,1074,1077],{},[150,1075,1076],{},"DPAPI 层的限制","：本机凭据的安全性最终依赖于 Windows 用户密码。如果 Windows 账户被破解，攻击者用该账户登录电脑，DPAPI 解密会自动进行。如果用户以管理员身份运行工具（虽然不需要管理员权限），攻击者获得管理员权限后理论上也可能绕过某些保护。安全链的强度由最弱的一环决定——如果你的 Windows 用户密码很弱，或者电脑物理上被他人访问，DPAPI 的保护就名存实亡。",[10,1079,1080,1083],{},[150,1081,1082],{},"备份密码的强度","：导入备份时用户设置的密码决定了备份的抗暴力破解能力。PBKDF2 提供的防护再强，也无法弥补一个简单密码的缺陷。\"123456\"这样的备份密码，在 600,000 次迭代和现代 GPU 的破解能力面前，可能在几秒到几分钟内被破解。",[10,1085,1086,1089],{},[150,1087,1088],{},"系统策略的约束","：工具运行在 Windows 系统上，不会绕过任何系统级的安全机制。Windows SmartScreen 对未签名程序的警告、远程桌面连接的安全确认对话、Group Policy 的限制——这些工具都无法绕过。安装包为未签名的内部制品，Windows SmartScreen 会在首次运行时显示\"未知发布者\"警告。这不是工具的缺陷，而是系统安全策略的正常行为。",[10,1091,1092,1095,1096,1099],{},[150,1093,1094],{},"加密设计的范围","：这套两层加密设计防的是",[150,1097,1098],{},"离线攻击","——攻击者获得了备份文件或本机的加密数据，在没有用户交互的情况下尝试破解。它防不了的情况：",[104,1101,1102,1105,1108,1111],{},[43,1103,1104],{},"备份密码通过社工或偷看被直接获取",[43,1106,1107],{},"备份文件在网络传输过程中被中间人截获（如果使用不安全的传输方式）",[43,1109,1110],{},"凭据被恶意软件在内存中窃取（工具启动后、密码解密到内存这段时间内）",[43,1112,1113],{},"Windows 账户本身被已经登录电脑的恶意软件控制",[10,1115,1116],{},"对这些风险的防护需要用户在安全习惯和网络安全措施上自行补足——设置强密码、在信任的网络上操作、定期更新系统补丁、使用反恶意软件工具。",[10,1118,1119],{},"使用这套工具前要明确：本机凭据带来了便利，代价是将安全依赖在 Windows 用户身份上；备份凭据提供了迁移能力，代价是密码强度必须由用户自己把关。都不是\"一次设置永久安全\"的方案，都需要持续的安全意识和维护。",{"title":35,"searchDepth":475,"depth":475,"links":1121},[1122,1123,1124,1125],{"id":1005,"depth":475,"text":1006},{"id":1035,"depth":475,"text":1036},{"id":1048,"depth":475,"text":1048},{"id":1068,"depth":475,"text":1068},"2026-07-20",{},"\u002F2026-07-20-dpapi60",{"title":997,"description":1002},"2026-07-20-远程桌面管理器DPAPI与60万次迭代","两层加密保护远程桌面凭据，本机用 DPAPI 便利性换易用性，备份用 PBKDF2 固定 60 万迭代；为什么迭代次数不可配置，性能数据如何解读。",[1133,1134,1135,1136,1137],"Windows","DPAPI","PBKDF2","凭据存储","加密设计","TzKUoZoFLtAsO_VcB1TOOdWOpwcoDj6AUwEdS6d7pv0",{"id":1140,"title":1141,"body":1142,"column":671,"date":1261,"description":1146,"extension":673,"hero_image":674,"meta":1262,"navigation":676,"path":1263,"seo":1264,"series_id":674,"severity":674,"stem":1265,"summary":1266,"tags":1267,"__hash__":1273},"posts\u002F2026-07-17-把设计文档变成可讲的演示.md","转不成演示的设计文档，本来就没讲清",{"type":7,"value":1143,"toc":1255},[1144,1147,1153,1156,1159,1162,1165,1183,1190,1194,1197,1203,1213,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252],[10,1145,1146],{},"一年积累了 200 多份设计文档，最初想法是直接拿这些文档去讲。结果发现这些东西不适合讲。设计文档是给人写的，演示文稿是给人听的，形式完全不同。",[10,1148,1149,1150,171],{},"解决这个问题的思路不是\"写个通用 PPT 编辑器\"，而是\"从结构化文档一键生成演示\"。本质差异在这里——编辑器要处理用户的任意编辑行为，演示工具只要转换",[150,1151,1152],{},"已有的结构",[14,1154,1155],{"id":1155},"为什么选单向转换而不是编辑器",[10,1157,1158],{},"设计文档有稳定的模板：背景、方案、架构、取舍、参考。这个顺序不是随意的，恰好就是讲一个设计时的叙述顺序。",[10,1160,1161],{},"用户不需要\"先生成再改\"，需要的是\"文档秒变幻灯片\"。一旦你改，就回到编辑器的坑里去了——要支持拖拽、删除、排版，工作量爆炸，而且多数用户不会调，生成好的东西就是定版。",[10,1163,1164],{},"这个判断来自实际数据。我的工具做两个决策：",[40,1166,1167,1177],{},[43,1168,1169,1172,1173,1176],{},[150,1170,1171],{},"产物是自包含 HTML","，不是 Office 文件（",[22,1174,1175],{},".pptx"," 需要可编辑格式，门槛高；HTML 在浏览器里就能放映，自包含意味着内联了所有 CSS、JS、图片）。",[43,1178,1179,1182],{},[150,1180,1181],{},"内容由 LLM 生成，不开放编辑面板","（用一个\"预览挑选\"的两阶段流程，让用户在 3 种风格的封面里选一个，然后生成整份）。",[10,1184,1185,1186,1189],{},"这两个约束听起来很严格，实际上契合了需求的本质：",[150,1187,1188],{},"文档的目的是记录决策，演示的目的是讲述决策","。不需要演示过程中再改决策，改了就回到文档去改。",[14,1191,1193],{"id":1192},"什么样的结构能转什么样的不能","什么样的结构能转、什么样的不能",[10,1195,1196],{},"设计文档要是能一键转成演示，必须满足几个条件。",[10,1198,1199,1202],{},[150,1200,1201],{},"架构图可以直接映射","。我的演示工具支持 AI 生图，也支持用户指定图片 URL。当文档里写了架构设计时，LLM 看到这个描述会生成对应的图，然后内联到演示文稿里。舞台是固定的 1920×1080，所有图片容器有最小尺寸约束（GSAP 时间轴动画要能在任意时间点求值，不能靠动态布局）。",[10,1204,1205,1208,1209,1212],{},[150,1206,1207],{},"关键数据用表格记录最不容易犯错","。表格这种结构容易转化——",[168,1210,1211],{},"待补：具体表格如何转成演示页的实现规则","。如果文档里的数据以段落文字形式写，转成演示时就卡住了，LLM 要从自然语言反推结构。",[10,1214,1215,1218,1219],{},[150,1216,1217],{},"取舍（trade-off）部分","：",[168,1220,1221],{},"待补：设计文档中的取舍说明如何映射成演示内容的规则",[10,1223,1224],{},"一个更深的观察：文档转不出来好演示，通常不是演示工具的问题，是文档本身没讲清楚。",[10,1226,1227],{},"我见过一类项目文档，有 20 多份模板文件，但只要换个配色其他全一样。这说明什么？说明那些\"模板\"实际上没有结构差异，只有视觉差异。结构不清，自然转不出演示。或者反过来说，如果要证明自己的文档结构是清晰的，试试能不能一键转成演示——转不出来就是信号，说明需要先梳理文档本身。",[14,1229,1230],{"id":1230},"工具的硬约束",[10,1232,1233],{},"演示不同于其他产物，有独特的硬约束。我的工具选择了 1920×1080 的固定舞台，所有动画用 GSAP 时间轴。这些看起来像限制，实际上是为了保证可靠性。",[10,1235,1236],{},"固定舞台尺寸意味着设计者写风格预设时要算好留白和排版，不能寄希望于\"容器自适应就行了\"。GSAP 时间轴的特点是能在任意时间点求值（渲染管线会 seek 到任意帧截图），不能用 CSS animation 这种依赖真实时间流逝的东西。这限制了动效，但换来的是确定性——动效一定会在预期时间点发生，不会因为网络卡而错位。",[10,1238,1239],{},"生成流程分两个阶段，有个细节很实用。第一阶段只生成 3 个风格的封面单页，第二阶段才生成整份演示文稿。这个设计看似多一步，实际上优雅地解决了两个问题。一是给用户选择风格的机会（不是非此即彼的\"生成或不生成\"）；二是掩盖异步生图的等待时间（生图 1-2 分钟，正好被用户在选择封面时吸收了）。",[14,1241,1242],{"id":1242},"从文档能否转演示看结构清晰度",[10,1244,1245],{},"最后回到起点。为什么要做这个工具？",[10,1247,1248],{},"表面原因是 200 多份文档用演讲方式讲会更有力。深层原因是这个过程本身就是对文档质量的检验。",[10,1250,1251],{},"一份设计文档如果结构清晰——背景交代得清，方案对比得充分，架构图画得明确，取舍理由说得透彻——那转成演示就是平移内容，不费劲。转不出来、或者转出来很别扭，就是信号，说明某个环节讲得不够好。",[10,1253,1254],{},"这个反馈机制比任何 review 注释都直白。\"这个表述为什么转不成幻灯片？\"往往能逼出真实的问题——\"哦，因为我其实还没想清楚这个方案为什么比另一个好\"。",{"title":35,"searchDepth":475,"depth":475,"links":1256},[1257,1258,1259,1260],{"id":1155,"depth":475,"text":1155},{"id":1192,"depth":475,"text":1193},{"id":1230,"depth":475,"text":1230},{"id":1242,"depth":475,"text":1242},"2026-07-17",{},"\u002F2026-07-17",{"title":1141,"description":1146},"2026-07-17-把设计文档变成可讲的演示","不做通用PPT编辑器，只做文档→演示的单向转换；关键在识别文档的稳定结构。",[1268,1269,1270,1271,1272],"演示文稿","文档结构","设计工具","HTML","GSAP","Reh87_TKXZ8nl5BhDhHzVOONAzwRS-Im8LTYrIfeuNk",1785406912235]