<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>w2solo - 独立开发者社区</title>
    <link>http://www.w2solo.com/</link>
    <description>w2solo - 独立开发者社区社区最新发帖.</description>
    <language>en-us</language>
    <item>
      <title>讯飞星辰 Maas 福利活动，3.9 元 30 天 API 不限量调用!</title>
      <description>&lt;p&gt;养过 OpenClaw（小龙虾）的都知道有多烧钱——后台 24 小时跑任务，按 Token 计费，一天上千块是常事。更坑的是哪怕没盯着它，AI 智能体也会在后台自动执行、默默消耗 Token，不知不觉账单就爆表了。&lt;/p&gt;

&lt;p&gt;二手平台上现在有个魔幻现象：帮人卸载小龙虾的订单比安装的还火爆。十几块、几十块帮人卸载，第一批踩坑的人纷纷止损离场。&lt;/p&gt;

&lt;p&gt;但科大讯飞旗下的讯飞星辰 MaaS 平台，直接把这个痛点给治了。
&lt;img src="https://gitee.com/da-qiang-classmate/typora/raw/master/image/cover-iflytek-maas-20260518-230651.webp" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="按请求次数收费"&gt;按请求次数收费&lt;/h3&gt;
&lt;p&gt;以前所有 AI 平台都是 &lt;strong&gt;按 Token 收费&lt;/strong&gt;——写的内容越长、任务越复杂、跑的时间越长，费用蹭蹭往上涨。&lt;/p&gt;

&lt;p&gt;讯飞星辰这次把计费模式从"按 Token 收费"改成了 &lt;strong&gt;"按请求次数收费"&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;听起来都是收费？差别巨大：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tr&gt;
&lt;th style="text-align:left;"&gt;对比项&lt;/th&gt;
&lt;th style="text-align:left;"&gt;按 Token 收费&lt;/th&gt;
&lt;th style="text-align:left;"&gt;按请求次数收费&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:left;"&gt;复杂任务&lt;/td&gt;
&lt;td style="text-align:left;"&gt;Token 瞬间激增&lt;/td&gt;
&lt;td style="text-align:left;"&gt;只算 1 次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:left;"&gt;自动化任务&lt;/td&gt;
&lt;td style="text-align:left;"&gt;跑得越久越贵&lt;/td&gt;
&lt;td style="text-align:left;"&gt;不管跑多久都一样&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:left;"&gt;成本可控性&lt;/td&gt;
&lt;td style="text-align:left;"&gt;完全不可控&lt;/td&gt;
&lt;td style="text-align:left;"&gt;一眼看得到底&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;做一整套番茄时钟网站也好、让 AI 每天定时搜集全网热点也罢，全程只消耗 1 次请求。&lt;/p&gt;
&lt;h3 id="3.9 元不限量"&gt;3.9 元不限量&lt;/h3&gt;
&lt;p&gt;平台整合了 &lt;strong&gt;60 多款热门大模型&lt;/strong&gt;：GLM5.1、Qwen3.5、Kimi2.5……一个入口随便切换，不用来回注册多个平台。&lt;/p&gt;

&lt;p&gt;最狠的是现在的活动价：&lt;strong&gt;3.9 元 = 整个月不限量随便跑&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;一瓶可乐的钱，整个月随便用。不管任务多复杂、跑多少次自动化，成本完全锁定在 3.9 元以内。&lt;/p&gt;

&lt;p&gt;职业开发者或企业用户还可以选专业版套餐，搭配 5 小时滑动窗口 + 周 / 月重置流控策略，专为 AI 智能体这类场景量身定制。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://gitee.com/da-qiang-classmate/typora/raw/master/image/dc95fed86e7d68bb23abde242db6146a.webp" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="如何订阅"&gt;如何订阅&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;订阅地址&lt;/strong&gt;：
&lt;a href="https://maas.xfyun.cn/packageSubscription?inviteCode=MAAS-F75A78EC" rel="nofollow" target="_blank"&gt;https://maas.xfyun.cn/packageSubscription?inviteCode=MAAS-F75A78EC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;订阅前填邀请码：&lt;strong&gt;MAAS-F75A78EC&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;填邀请码后双方都能拿到等额礼品卡，可直接抵扣 API 费用。相当于白嫖套餐。&lt;/p&gt;

&lt;p&gt;限时活动，3.9 元解锁不限量套餐，手慢无。以前养虾像养吞金兽，现在一瓶可乐搞定一个月。&lt;/p&gt;

&lt;p&gt;工具的意义不在于它有多牛，在于它让你把时间花在真正该花的地方。&lt;/p&gt;</description>
      <author>sphinx30</author>
      <pubDate>Mon, 18 May 2026 23:22:00 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7356</link>
      <guid>http://www.w2solo.com/topics/7356</guid>
    </item>
    <item>
      <title>一口气讲清楚 Monorepo、Turborepo、pnpm、Changesets 到底是什么？</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;你肯定遇到过这种情况：项目里同时有前端、后端、公共组件，放在一个仓库嫌乱，拆成多个仓库又改一个公共函数要在五个项目里各改一遍。于是出现了 Monorepo、Turborepo、pnpm、Changesets 这四个词。它们不是互相替代，而是分别解决工程化中不同层面的问题。读完之后，你会明白它们各自解决什么、技术原理是什么、彼此之间是什么关系，以及在实际项目中该如何组合使用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr&gt;
&lt;h2 id="一、先搞清楚一件事：为什么会有这些工具？"&gt;一、先搞清楚一件事：为什么会有这些工具？&lt;/h2&gt;
&lt;p&gt;前端工程化发展到今天，一个中型项目往往包含多个应用（Web、小程序、Node 服务）和多个共享包（UI 组件库、工具函数、类型定义）。传统的多仓库（Polyrepo）模式有两个致命痛点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;代码复用难&lt;/strong&gt;：改一个公共函数，要在 5 个仓库里各改一遍，还要各自发版。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;依赖管理乱&lt;/strong&gt;：每个仓库都重复安装 &lt;code&gt;react&lt;/code&gt;、&lt;code&gt;lodash&lt;/code&gt;，磁盘空间爆炸，版本不同步还容易出 bug。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;于是工程界开始借鉴谷歌、Facebook 的做法，把多个项目放进&lt;strong&gt;同一个仓库&lt;/strong&gt;——这就是 Monorepo。但光放进去还不够，你还需要：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一个&lt;strong&gt;包管理器&lt;/strong&gt;来高效处理依赖（pnpm）&lt;/li&gt;
&lt;li&gt;一个&lt;strong&gt;构建编排器&lt;/strong&gt;来加速构建和任务执行（Turborepo）&lt;/li&gt;
&lt;li&gt;一个&lt;strong&gt;版本管理工具&lt;/strong&gt;来帮你自动发版和生成 changelog（Changesets）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这四个工具不是互相替代，而是互补的，分别解决前端工程化中不同层面的问题。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="二、Monorepo：把多个项目放进同一个“家”"&gt;二、Monorepo：把多个项目放进同一个 “家”&lt;/h2&gt;&lt;h3 id="2.1 一句话定义"&gt;2.1 一句话定义&lt;/h3&gt;
&lt;p&gt;Monorepo 是一种代码仓库组织策略，在&lt;strong&gt;一个 Git 仓库&lt;/strong&gt;里管理多个相互独立但又相互依赖的项目（应用、库、服务）。&lt;/p&gt;
&lt;h3 id="2.2 跟 Polyrepo 有什么区别？"&gt;2.2 跟 Polyrepo 有什么区别？&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;Polyrepo（多仓库）&lt;/th&gt;
&lt;th&gt;Monorepo（单仓库）&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;代码复用&lt;/td&gt;
&lt;td&gt;发布 npm 包或复制粘贴&lt;/td&gt;
&lt;td&gt;直接通过 &lt;code&gt;workspace&lt;/code&gt; 引用源码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;依赖管理&lt;/td&gt;
&lt;td&gt;每个仓库独立安装依赖，重复浪费&lt;/td&gt;
&lt;td&gt;依赖提升到根目录，一处安装全局使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;跨项目改动&lt;/td&gt;
&lt;td&gt;改一个公共函数需改 N 个仓库&lt;/td&gt;
&lt;td&gt;只需改一次，所有项目立即生效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;权限控制&lt;/td&gt;
&lt;td&gt;按仓库隔离，精细但麻烦&lt;/td&gt;
&lt;td&gt;可通过 CODEOWNERS 实现目录级权限&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD&lt;/td&gt;
&lt;td&gt;每个仓库单独构建，资源分散&lt;/td&gt;
&lt;td&gt;只构建受影响的项目，可并行执行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;学习成本&lt;/td&gt;
&lt;td&gt;低，各项目独立&lt;/td&gt;
&lt;td&gt;需理解 workspaces、任务编排等概念&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;h3 id="2.3 一个简单的目录结构"&gt;2.3 一个简单的目录结构&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-monorepo/
├── apps/              # 应用程序
│   ├── web/           # React 前端
│   ├── admin/         # 后台管理系统
│   └── api/           # Node 后端
├── packages/          # 共享包
│   ├── ui/            # 组件库
│   ├── utils/         # 工具函数
│   └── config/        # 共享配置（ESLint、TS）
├── package.json
├── pnpm-workspace.yaml # 工作区配置
└── turbo.json          # Turborepo 配置
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2.4 技术挑战"&gt;2.4 技术挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;依赖提升带来的幽灵依赖&lt;/strong&gt;：项目可能引用未在自身 &lt;code&gt;package.json&lt;/code&gt; 声明的包（因为被提升到了根目录），导致部署时遗漏依赖。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;构建性能&lt;/strong&gt;：随着项目增多，全量构建会越来越慢，需要增量构建和缓存。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;权限与协作&lt;/strong&gt;：需要合理的 CODEOWNERS 和分支策略，避免一个人改崩整个仓库。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="三、pnpm：比 npm 更聪明的包管理器"&gt;三、pnpm：比 npm 更聪明的包管理器&lt;/h2&gt;&lt;h3 id="3.1 一句话定义"&gt;3.1 一句话定义&lt;/h3&gt;
&lt;p&gt;pnpm 是一个高性能的包管理器，它通过&lt;strong&gt;内容可寻址存储&lt;/strong&gt;和&lt;strong&gt;符号链接&lt;/strong&gt;实现多项目间依赖的全局去重，比 npm/yarn 更快、更省磁盘空间。&lt;/p&gt;
&lt;h3 id="3.2 跟 npm / yarn 有什么区别？"&gt;3.2 跟 npm / yarn 有什么区别？&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;npm / yarn（传统）&lt;/th&gt;
&lt;th&gt;pnpm&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;依赖存储&lt;/td&gt;
&lt;td&gt;每个项目 &lt;code&gt;node_modules&lt;/code&gt; 都复制一份依赖&lt;/td&gt;
&lt;td&gt;全局 store 存储一份，通过硬链接复用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;磁盘占用&lt;/td&gt;
&lt;td&gt;100 个项目 = 100 份 react&lt;/td&gt;
&lt;td&gt;100 个项目 = 1 份 react&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;安装速度&lt;/td&gt;
&lt;td&gt;慢，重复下载&lt;/td&gt;
&lt;td&gt;快，已下载过的直接从缓存链接&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;幽灵依赖&lt;/td&gt;
&lt;td&gt;存在（项目可访问未声明的包）&lt;/td&gt;
&lt;td&gt;不存在，严格的依赖隔离&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monorepo 支持&lt;/td&gt;
&lt;td&gt;需要 &lt;code&gt;workspaces&lt;/code&gt; 配置&lt;/td&gt;
&lt;td&gt;原生支持，通过 &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;h3 id="3.3 怎么配置 pnpm workspace？"&gt;3.3 怎么配置 pnpm workspace？&lt;/h3&gt;
&lt;p&gt;在项目根目录创建 &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps/*"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;packages/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后执行 &lt;code&gt;pnpm install&lt;/code&gt;。pnpm 会自动把 &lt;code&gt;apps/&lt;/code&gt; 和 &lt;code&gt;packages/&lt;/code&gt; 下的每个子目录当作一个 workspace 包，并通过符号链接让它们互相引用。&lt;/p&gt;
&lt;h3 id="3.4 常用命令"&gt;3.4 常用命令&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt;                 &lt;span class="c"&gt;# 安装所有依赖&lt;/span&gt;
pnpm add react &lt;span class="nt"&gt;-w&lt;/span&gt;            &lt;span class="c"&gt;# 给根目录添加依赖（-w 表示 workspace root）&lt;/span&gt;
pnpm &lt;span class="nt"&gt;--filter&lt;/span&gt; web add lodash &lt;span class="c"&gt;# 只给 web 应用添加 lodash&lt;/span&gt;
pnpm &lt;span class="nt"&gt;--filter&lt;/span&gt; web dev        &lt;span class="c"&gt;# 只运行 web 应用的 dev 脚本&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3.5 技术挑战"&gt;3.5 技术挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;原生工具链兼容性&lt;/strong&gt;：一些旧的 npm 脚本或工具假设 &lt;code&gt;node_modules&lt;/code&gt; 是平铺结构，在 pnpm 下可能不工作（可通过 &lt;code&gt;shamefully-hoist&lt;/code&gt; 解决）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;学习成本&lt;/strong&gt;：开发者需要理解 &lt;code&gt;--filter&lt;/code&gt;、workspace 协议（&lt;code&gt;"ui": "workspace:*"&lt;/code&gt;）等概念。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="四、Turborepo：让构建任务“快如闪电”"&gt;四、Turborepo：让构建任务 “快如闪电”&lt;/h2&gt;&lt;h3 id="4.1 一句话定义"&gt;4.1 一句话定义&lt;/h3&gt;
&lt;p&gt;Turborepo 是一个&lt;strong&gt;高性能的任务编排器&lt;/strong&gt;，专门为 Monorepo 设计。它会缓存每个任务的输入输出，第二次运行相同输入时直接跳过执行，从而实现秒级重构建。&lt;/p&gt;
&lt;h3 id="4.2 跟普通 npm run 脚本有什么区别？"&gt;4.2 跟普通 &lt;code&gt;npm run&lt;/code&gt; 脚本有什么区别？&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;普通脚本&lt;/th&gt;
&lt;th&gt;Turborepo&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;执行方式&lt;/td&gt;
&lt;td&gt;按顺序串行执行&lt;/td&gt;
&lt;td&gt;自动并行执行（依赖关系不变）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;缓存&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;内容寻址缓存，相同输入直接返回缓存结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;增量构建&lt;/td&gt;
&lt;td&gt;需要手动实现&lt;/td&gt;
&lt;td&gt;自动检测哪些项目变了，只构建受影响的部分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;远程缓存&lt;/td&gt;
&lt;td&gt;不支持&lt;/td&gt;
&lt;td&gt;支持云缓存，团队成员共享构建缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;依赖感知&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;自动识别 &lt;code&gt;dependsOn&lt;/code&gt;，按拓扑顺序构建&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;h3 id="4.3 Turborepo 工作原理"&gt;4.3 Turborepo 工作原理&lt;/h3&gt;
&lt;p&gt;Turborepo 用 &lt;strong&gt;管道（pipeline）&lt;/strong&gt; 定义任务之间的关系。一个典型的 &lt;code&gt;turbo.json&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://turbo.build/schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"globalDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"**/.env.*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pipeline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^build"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;先构建依赖包，再构建当前包&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dist/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".next/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cache"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;开发模式不缓存&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"persistent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^lint"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;先跑依赖包的&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lint&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;先&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;再&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;test&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;缓存机制&lt;/strong&gt;：Turbo 会计算任务输入（源代码、依赖的 task 输出、环境变量）的哈希值。如果哈希值没有变化，直接输出之前缓存的产物，执行时间从分钟级降为毫秒级。&lt;/p&gt;
&lt;h3 id="4.4 技术挑战"&gt;4.4 技术挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;缓存失效过于保守&lt;/strong&gt;：如果你的任务输入包含不必要的大文件（如 &lt;code&gt;node_modules&lt;/code&gt;），缓存命中率会很低。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;远程缓存需要服务器&lt;/strong&gt;：团队共享缓存需要自己部署或使用 Vercel 的 Remote Cache。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="五、Changesets：版本管理不再痛苦"&gt;五、Changesets：版本管理不再痛苦&lt;/h2&gt;&lt;h3 id="5.1 一句话定义"&gt;5.1 一句话定义&lt;/h3&gt;
&lt;p&gt;Changesets 是一个用于 Monorepo 的&lt;strong&gt;版本管理和 changelog 生成工具&lt;/strong&gt;。它让你在提交代码时记录变更意图，然后一键批量发布所有需要升级的包。&lt;/p&gt;
&lt;h3 id="5.2 为什么需要它？"&gt;5.2 为什么需要它？&lt;/h3&gt;
&lt;p&gt;在 Monorepo 中，你改了 &lt;code&gt;packages/utils&lt;/code&gt;，可能会同时影响 &lt;code&gt;apps/web&lt;/code&gt; 和 &lt;code&gt;apps/admin&lt;/code&gt;。如果手动去修改这些包的 &lt;code&gt;package.json&lt;/code&gt; 版本号，并各自生成 changelog，非常繁琐且容易漏。Changesets 自动化了这个流程。&lt;/p&gt;
&lt;h3 id="5.3 工作流程"&gt;5.3 工作流程&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;开发者改代码
    ↓
pnpm changeset      # 交互式选择要升级的包、填写变更描述
    ↓
生成 .changeset/*.md 文件（提交到 Git）
    ↓
CI / 发布时运行 pnpm changeset version
    ↓
自动升级版本号、更新 changelog、删除 .changeset 文件
    ↓
pnpm publish -r    # 发布所有变更的包到 npm
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="5.4 技术挑战"&gt;5.4 技术挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;与 CI/CD 集成&lt;/strong&gt;：需要在 PR 合并后自动运行 &lt;code&gt;version&lt;/code&gt; 命令并提交，需要配置 GitHub Actions 或 GitLab CI。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;依赖升级的传递性&lt;/strong&gt;：如果你改了底层包，上层包是否要强制升级？Changesets 可以自动处理，但需要正确配置 &lt;code&gt;updateInternalDependencies&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="六、四者的关系：一张图讲清楚"&gt;六、四者的关系：一张图讲清楚&lt;/h2&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tr&gt;
&lt;th&gt;工具&lt;/th&gt;
&lt;th&gt;角色定位&lt;/th&gt;
&lt;th&gt;解决的核心问题&lt;/th&gt;
&lt;th&gt;类比&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monorepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;代码组织策略&lt;/td&gt;
&lt;td&gt;多个项目如何放进同一个仓库&lt;/td&gt;
&lt;td&gt;盖一栋大楼（框架）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pnpm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;包管理器&lt;/td&gt;
&lt;td&gt;如何快速、节省空间地安装依赖&lt;/td&gt;
&lt;td&gt;大楼的水电管道系统&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Turborepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;任务编排器&lt;/td&gt;
&lt;td&gt;如何加速构建、测试、lint 等任务&lt;/td&gt;
&lt;td&gt;大楼的电梯调度系统&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Changesets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;版本管理&lt;/td&gt;
&lt;td&gt;如何自动化发版和生成 changelog&lt;/td&gt;
&lt;td&gt;大楼的物业管理系统&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;它们的协作关系：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;开发者修改代码（在 Monorepo 中）
        ↓
pnpm 负责安装依赖，链接 workspace 包
        ↓
Turborepo 负责按需执行任务（build、test、lint），利用缓存加速
        ↓
开发完成后，提交 PR
        ↓
PR 合并到 main 分支
        ↓
CI 运行 Changesets：自动升级版本、生成 changelog
        ↓
pnpm publish -r 发布到 npm
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id="七、技术选型指南：实际工程中怎么组合？"&gt;七、技术选型指南：实际工程中怎么组合？&lt;/h2&gt;&lt;h3 id="场景一：个人项目或小团队（2-5 人，3-5 个包）"&gt;场景一：个人项目或小团队（2-5 人，3-5 个包）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;推荐&lt;/strong&gt;：&lt;code&gt;pnpm + Monorepo&lt;/code&gt; 就够了，不需要 Turborepo（构建不慢）和 Changesets（手动改版本号也能接受）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;操作&lt;/strong&gt;：直接用 pnpm workspace，在根目录写几个 npm scripts 串行执行 &lt;code&gt;build&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="场景二：中型项目（5-20 人，10-20 个包，构建耗时 &amp;gt; 2 分钟）"&gt;场景二：中型项目（5-20 人，10-20 个包，构建耗时 &amp;gt; 2 分钟）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;推荐&lt;/strong&gt;：&lt;code&gt;pnpm + Turborepo + Monorepo&lt;/code&gt;，用 Turborepo 的缓存和并行能力加速 CI。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;版本管理&lt;/strong&gt;：可以暂时手动改版本，也可以用 Changesets 但非强制。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="场景三：大型项目 / 开源库（多人协作，频繁发版，包之间有复杂依赖）"&gt;场景三：大型项目 / 开源库（多人协作，频繁发版，包之间有复杂依赖）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;推荐&lt;/strong&gt;：&lt;code&gt;pnpm + Turborepo + Changesets + Monorepo&lt;/code&gt;，全套上齐。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;额外&lt;/strong&gt;：配置远程缓存（如 Vercel Remote Cache）让团队成员共享构建结果；设置 CI 自动执行 &lt;code&gt;changeset version&lt;/code&gt; 和 &lt;code&gt;publish&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="场景四：已有大量 npm 包，准备迁移到 Monorepo"&gt;场景四：已有大量 npm 包，准备迁移到 Monorepo&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;步骤&lt;/strong&gt;：先用 &lt;code&gt;pnpm import&lt;/code&gt; 把现有 &lt;code&gt;package-lock.json&lt;/code&gt; 转成 &lt;code&gt;pnpm-lock.yaml&lt;/code&gt;；然后逐步把相关仓库移入 &lt;code&gt;packages/&lt;/code&gt;，调整 import 路径；最后引入 Turborepo 优化 CI。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="💎 写在最后"&gt;💎 写在最后&lt;/h2&gt;
&lt;p&gt;回到最开始的问题：为什么需要 Monorepo、Turborepo、pnpm、Changesets 这四个工具？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monorepo&lt;/strong&gt; 给你一个容纳多项目的大房子。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pnpm&lt;/strong&gt; 给你高效的管道系统，让依赖管理快如闪电。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turborepo&lt;/strong&gt; 给你智能的电梯，让构建任务不再重复劳动。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changesets&lt;/strong&gt; 给你规范的物业管理，让版本发布井井有条。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;它们不是 “银弹”，但当你团队规模膨胀、项目耦合加深时，这套组合拳能让你从 “复制粘贴工程师” 进化为 “工程化架构师”。&lt;/p&gt;

&lt;p&gt;如果你也在搭建 Monorepo，或者被多仓库的代码复用问题折磨过，&lt;strong&gt;点个赞让我看到&lt;/strong&gt;。赞多的话，下一篇写 “如何从零落地一个 pnpm + Turborepo + Changesets 的 Monorepo 项目，包含完整 CI 配置”。&lt;/p&gt;</description>
      <author>193577746</author>
      <pubDate>Mon, 18 May 2026 20:05:05 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7355</link>
      <guid>http://www.w2solo.com/topics/7355</guid>
    </item>
    <item>
      <title>推荐一个自用很久的第三方 API 平台❗</title>
      <description>&lt;p&gt;我主要调用 gpt-image-2 给文章配图，成本 0.005/张，7 快能生成 200 张图。&lt;/p&gt;

&lt;p&gt;除了 GPT-Image-2 还支持各种模型调用。速度和稳定性包的，性价比很高。&lt;/p&gt;

&lt;p&gt;地址 &lt;a href="https://apimart.ai/register?aff=QVui" rel="nofollow" target="_blank"&gt;https://apimart.ai/register?aff=QVui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://img.way2solo.com/photo/sphinx30/c166cad9-f20e-4005-9422-86cc509cd43a.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>sphinx30</author>
      <pubDate>Mon, 18 May 2026 14:33:47 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7354</link>
      <guid>http://www.w2solo.com/topics/7354</guid>
    </item>
    <item>
      <title>我做了一个拼图 + 图片工具小程序「奇伴拼图」</title>
      <description>&lt;p&gt;最近在做一个很轻的小程序，叫 奇伴拼图。
&lt;img src="https://img.way2solo.com/photo/onling/7f7ee35e-ee47-42a4-82f9-d6da8250e8bd.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图片
想用 #buildinpublic 记录一下这个项目的从 0 到 1。&lt;/p&gt;

&lt;p&gt;🧠最开始的想法其实很简单
我一直在想一个问题：&lt;/p&gt;

&lt;p&gt;为什么 “放松” 和 “工具” 总是分开的？&lt;/p&gt;

&lt;p&gt;• 想放松 → 去刷短视频 / 玩小游戏
• 想做事 → 打开图片工具 / 修图软件
但真实情况是：&lt;/p&gt;

&lt;p&gt;👉 很多时候，我们只是想 “切换一下状态”&lt;/p&gt;

&lt;p&gt;不是娱乐，也不是工作。&lt;/p&gt;

&lt;p&gt;🧩于是我做了这个小东西
「奇伴拼图」目前就两个部分：&lt;/p&gt;

&lt;p&gt;🎮 ① 拼图模式（用来放空）
• 每日一张挑战图
• 多种难度（3x3 / 5x5 / 9x9）
• 没有复杂规则
• 纯粹 “慢下来拼一会”
👉 目标不是赢，而是让脑子安静一点&lt;/p&gt;

&lt;p&gt;🖼️ ② 图片工具（顺手解决问题）
• 图片拼接
• 长图合成
• 简单排版
👉 用来处理一些日常 “顺手的需求”&lt;/p&gt;

&lt;p&gt;🧪做这个过程中最大的感受
1️⃣ 用户不区分 “工具 or 游戏”
他们只在乎：&lt;/p&gt;

&lt;p&gt;“我现在用这个，是不是舒服一点？”&lt;/p&gt;

&lt;p&gt;2️⃣ 小程序的本质是 “低决策成本”
越复杂 → 越难留住用户
越简单 → 越容易被打开&lt;/p&gt;

&lt;p&gt;3️⃣ 拼图其实是个 “很短的体验产品”
它不是长时间使用的东西
而是一个 “状态切换器”&lt;/p&gt;

&lt;p&gt;📊目前进度
• 已上线微信小程序
图片&lt;/p&gt;

&lt;p&gt;• 功能还非常 MVP
• 没有做复杂设计
• 还在验证阶段
🔭下一步在想的事
• 要不要做 “每日挑战体系”
• 要不要加轻社交（排行榜）
• 是否拆成两个产品
• 留存怎么做才自然
💬这篇只是开始
想记录一下：&lt;/p&gt;

&lt;p&gt;一个很轻的小产品，是怎么一点点被做出来的。&lt;/p&gt;

&lt;p&gt;也欢迎任何做过类似产品的朋友一起交流 🙌
欢迎体验
&lt;img src="https://img.way2solo.com/photo/onling/3b43f6cb-b340-4e94-8623-ce57c1bf446d.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>onling</author>
      <pubDate>Mon, 18 May 2026 12:26:30 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7353</link>
      <guid>http://www.w2solo.com/topics/7353</guid>
    </item>
    <item>
      <title>用户打开飞行模式都能打开你的网站？Service Worker 做离线缓存，PWA 实战</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;你坐飞机，关掉网络，旁边小哥还在刷抖音（离线缓存好的视频）。你打开自己的网站，白屏，报错。你默默关上手机，心想：“要是我的网站也能离线看就好了。” 今天我们就来给你的网站装上 “离线小精灵”——Service Worker。以后用户没网也能访问，还能把网站装到手机桌面，像原生 App 一样。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://img.way2solo.com/photo/193577746/9944dba9-3423-4021-8437-5c60c5035851.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;PWA（Progressive Web App）这个概念喊了好几年，但真正用上的网站不多。其实它没那么玄乎，核心就是 &lt;strong&gt;Service Worker&lt;/strong&gt;——一个在浏览器后台独立运行的 JS 线程，能拦截网络请求、缓存资源、推送通知。&lt;/p&gt;

&lt;p&gt;加了 Service Worker 的网站，就算用户开飞行模式，只要之前访问过，照样能看到页面（至少看到缓存过的内容）。而且速度极快，因为资源从本地取，不用等网络。今天我们就从零给一个静态网站加上离线缓存，顺便让它 “可安装”。&lt;/p&gt;
&lt;h2 id="一、Service Worker 生命周期：四步走"&gt;一、Service Worker 生命周期：四步走&lt;/h2&gt;
&lt;p&gt;Service Worker 不是一上来就接管所有请求的，它有严格的生命周期：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;注册&lt;/strong&gt;：主线程告诉浏览器：“嘿，去下载这个 sw.js 文件。”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;安装&lt;/strong&gt;：浏览器下载、解析、执行 sw.js 里的 &lt;code&gt;install&lt;/code&gt; 事件。通常在这里缓存核心资源。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;激活&lt;/strong&gt;：旧 Service Worker 被替换，新 SW 接管控制权。可以在 &lt;code&gt;activate&lt;/code&gt; 事件里清理旧缓存。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;空闲/运行&lt;/strong&gt;：之后所有 fetch 请求都会被 SW 拦截。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注意：SW 只在 HTTPS（或 localhost）下生效，因为可以拦截网络，不安全。&lt;/p&gt;
&lt;h2 id="二、最简单的 Service Worker：离线回退页面"&gt;二、最简单的 Service Worker：离线回退页面&lt;/h2&gt;
&lt;p&gt;我们先写一个极简版 &lt;code&gt;sw.js&lt;/code&gt;，让用户离线时看到一个 “你已离线” 的页面。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// sw.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CACHE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-pwa-cache-v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OFFLINE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/offline.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 安装时缓存离线页面&lt;/span&gt;
&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;install&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CACHE_NAME&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OFFLINE_URL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// 强制等待中的 SW 立即激活&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;skipWaiting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 激活时清理旧缓存&lt;/span&gt;
&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;activate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;CACHE_NAME&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 拦截请求，离线时返回缓存&lt;/span&gt;
&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;navigate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 页面导航请求&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OFFLINE_URL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 其他资源走缓存优先策略（稍后优化）&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 &lt;code&gt;index.html&lt;/code&gt; 里注册：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serviceWorker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sw.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SW 注册成功&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SW 注册失败&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在你打开网站，开飞机模式（或 DevTools → Network 离线），刷新页面，应该会显示 &lt;code&gt;offline.html&lt;/code&gt;。说明 SW 已经拦下了请求。&lt;/p&gt;
&lt;h2 id="三、缓存策略：别把所有鸡蛋放一个篮子"&gt;三、缓存策略：别把所有鸡蛋放一个篮子&lt;/h2&gt;
&lt;p&gt;上面的代码对所有资源都用了 “缓存优先”——先查 cache，没有才网络。这会导致一个问题：如果某个资源之前缓存过，即使服务器更新了，用户也看不到新版本。所以需要根据资源类型选择策略。&lt;/p&gt;

&lt;p&gt;常用策略：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache First（缓存优先）&lt;/strong&gt;：适合不常变的图片、字体、CSS 库。速度快。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network First（网络优先）&lt;/strong&gt;：适合 API 数据、HTML 页面。先尝试网络，失败再读缓存。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale-While-Revalidate&lt;/strong&gt;：先返回缓存（如果有），同时后台更新缓存。兼顾速度和新鲜度。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;仅网络&lt;/strong&gt;：永远不缓存（如支付接口）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;仅缓存&lt;/strong&gt;：永远从缓存取（如离线页面）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们改一下 &lt;code&gt;fetch&lt;/code&gt; 事件：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// 如果是 API 请求，走网络优先&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 如果是静态资源（js、css、图片），走缓存优先&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;js|css|png|jpg|webp&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 其他（如 HTML）走 stale-while-revalidate&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CACHE_NAME&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;fetchPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，你的网站既能离线访问，又能及时更新动态内容。&lt;/p&gt;
&lt;h2 id="四、用 Workbox 简化代码"&gt;四、用 Workbox 简化代码&lt;/h2&gt;
&lt;p&gt;手写缓存策略很麻烦，尤其还要处理版本、过期、缓存清理。Google 出品了 &lt;strong&gt;Workbox&lt;/strong&gt;，一套工具库，几行配置搞定复杂策略。&lt;/p&gt;

&lt;p&gt;安装 Workbox CLI 或直接在 sw.js 里导入 CDN：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;importScripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cacheableResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 预缓存静态资源（构建时生成 manifest）&lt;/span&gt;
&lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;precaching&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;precacheAndRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__WB_MANIFEST&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="c1"&gt;// 图片缓存策略&lt;/span&gt;
&lt;span class="nx"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CacheFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;cacheName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cacheableResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CacheableResponsePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ExpirationPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;maxEntries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxAgeSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// API 网络优先&lt;/span&gt;
&lt;span class="nx"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NetworkFirst&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配合 webpack/vite 插件，可以自动生成预缓存清单，连 &lt;code&gt;install&lt;/code&gt; 里的 &lt;code&gt;cache.add&lt;/code&gt; 都不用手动写。&lt;/p&gt;
&lt;h2 id="五、让网站可安装（添加到主屏幕）"&gt;五、让网站可安装（添加到主屏幕）&lt;/h2&gt;
&lt;p&gt;PWA 另一大特性：用户可以像装 App 一样把网站装到手机桌面。需要满足三个条件：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS&lt;/strong&gt;（或 localhost）&lt;/li&gt;
&lt;li&gt;注册了 Service Worker&lt;/li&gt;
&lt;li&gt;有一个 &lt;code&gt;manifest.json&lt;/code&gt; 文件，放在根目录&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;示例 &lt;code&gt;manifest.json&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"我的离线网站"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"short_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"离线站"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standalone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"theme_color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"background_color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#ffffff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/icon-192.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"192x192"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/png"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/icon-512.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"512x512"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/png"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;index.html&lt;/code&gt; 里引用：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"manifest"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/manifest.json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后用户访问网站，浏览器会在地址栏右侧弹出 “安装 App” 的提示。点一下，桌面就多了一个图标，打开后没有浏览器地址栏，像原生 App。&lt;/p&gt;
&lt;h2 id="六、推送通知（可选彩蛋）"&gt;六、推送通知（可选彩蛋）&lt;/h2&gt;
&lt;p&gt;Service Worker 还能接收服务器推送的消息，即使网站没打开也能弹出通知。这需要用户授权和后台推送服务（比如 Firebase Cloud Messaging）。代码稍复杂，但可以实现 “用户关掉浏览器，你也能给他发优惠券提醒” 的效果。&lt;/p&gt;
&lt;h2 id="七、实测数据：加了 SW 之后"&gt;七、实测数据：加了 SW 之后&lt;/h2&gt;
&lt;p&gt;我用一个 React 静态网站测试：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;未缓存：首次加载 1.8s，二次无网白屏。&lt;/li&gt;
&lt;li&gt;加了 Workbox 预缓存：首次 2.0s（多下载了 SW 和 manifest），二次无网打开 0.3s（完全离线）。&lt;/li&gt;
&lt;li&gt;页面切换速度提升明显，因为路由对应的 JS 也被缓存。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用户从 “等待加载” 变成 “秒开”，体验提升 5 倍以上。&lt;/p&gt;
&lt;h2 id="八、坑点与避坑"&gt;八、坑点与避坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;更新缓存&lt;/strong&gt;：修改文件后，用户可能还是旧版本。需要更新 &lt;code&gt;CACHE_NAME&lt;/code&gt; 版本号，或者在预缓存时用 &lt;code&gt;rev&lt;/code&gt;（文件 hash）解决。Workbox 会自动处理。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localhost 测试&lt;/strong&gt;：记得勾选 DevTools → Application → Service Workers → Update on reload，否则 SW 缓存会干扰。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;作用域&lt;/strong&gt;：SW 默认作用域是 &lt;code&gt;sw.js&lt;/code&gt; 所在目录，如果放在根目录，可以控制全站。放在 &lt;code&gt;js/&lt;/code&gt; 下就只能控制 &lt;code&gt;js/&lt;/code&gt; 路径。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;调试&lt;/strong&gt;：Chrome DevTools 的 Application 面板可以看到所有缓存、SW 状态、推送通知。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="九、总结：PWA 是前端的“离线外挂”"&gt;九、总结：PWA 是前端的 “离线外挂”&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Service Worker 是浏览器后台独立线程，能拦截请求、缓存资源、推送通知。&lt;/li&gt;
&lt;li&gt;生命周期：注册 → 安装 → 激活 → fetch。&lt;/li&gt;
&lt;li&gt;缓存策略根据资源类型选择：Cache First、Network First、Stale-While-Revalidate。&lt;/li&gt;
&lt;li&gt;搭配 Workbox 可省去手写复杂缓存逻辑。&lt;/li&gt;
&lt;li&gt;加上 manifest.json 就能让网站 “安装到桌面”。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下次你坐飞机，打开自己的 PWA 网站，不用网络也能刷内容。同事看了问：“你怎么做到的？” 你就可以把本文甩给他。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;评论区聊聊：你的网站支持离线访问吗？遇到过哪些缓存更新问题？&lt;/p&gt;</description>
      <author>193577746</author>
      <pubDate>Mon, 18 May 2026 12:08:27 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7352</link>
      <guid>http://www.w2solo.com/topics/7352</guid>
    </item>
    <item>
      <title>TG 电报遇到 SMSfee，收不到短信验证码？我是这样登录成功的！</title>
      <description>&lt;p&gt;最近登录 Telegram 时遇到了 SMSfee 和验证码收不到的问题，折腾了一圈终于解决了。记录一下排查过程，希望能帮到同样卡住的朋友。
一、问题现象
前几天登录 Telegram 时，遇到了以下情况：&lt;/p&gt;

&lt;p&gt;输入手机号后，提示 "SMS fee" 
点击发送验证码，手机迟迟收不到短信&lt;/p&gt;

&lt;p&gt;语音验证码选项有时也打不开
一开始以为是运营商问题，但换了几张卡（移动/联通/电信）都一样，说明不是单个号码的问题。&lt;/p&gt;

&lt;p&gt;二、原因分析
查了一下 Telegram 的官方文档和社区讨论，大概搞清楚了原因：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;运营商层面的短信拦截
国内运营商对国际短信有风控策略，尤其是来自 Telegram 这类平台的验证码短信，很容易被系统自动拦截或延迟。这不是"墙"的问题，而是短信网关层面的过滤。&lt;/li&gt;
&lt;li&gt;Telegram 的防滥用机制
Telegram 对频繁尝试登录的号码会触发保护，表现为：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;要求支付 SMS fee（防止虚拟号滥用）&lt;/p&gt;

&lt;p&gt;直接限制该号码接收验证码一段时间&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;网络环境的影响
虽然短信本身走运营商通道，但 Telegram 的登录请求需要连接到 TG 服务器。如果初始握手阶段网络不稳定，可能导致服务端认为请求异常，从而拒绝发送验证码。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;三、结果
他的发现完美解决了我目前所处的困境！&lt;/p&gt;

&lt;p&gt;装完打开，得到手机号、输入验证码和二次密码，进去了。全程没见到那个付费提示，大概两分钟。&lt;/p&gt;

&lt;p&gt;用了几天，几个感受：&lt;/p&gt;

&lt;p&gt;界面是中文的，频道内容也是中文，不用自己折腾汉化 。网络连得上，不用手动填参数。聊天、收消息、看频道、切账号，日常用的功能都在，消息推送也正常。&lt;/p&gt;

&lt;p&gt;这次折腾最大的收获是：遇到系统性阻断，别跟死链路较劲，换个入口更省时间。有同样卡住的朋友，可以评论区交流下经验。
&lt;img src="https://img.way2solo.com/photo/huo/2696fd6e-99a6-4093-a9c1-ae7a6549f3a2.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/huo/52c22af4-cab9-4d3a-941a-9fe0782b672c.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/huo/04590412-f453-4a89-a6bd-665eaf292683.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>huo</author>
      <pubDate>Mon, 18 May 2026 11:56:36 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7351</link>
      <guid>http://www.w2solo.com/topics/7351</guid>
    </item>
    <item>
      <title>我做了一个 AI Motion Control 的 Saas 工具站，将任何参考视频中的动作迁移到另外的视频角色中</title>
      <description>&lt;p&gt;我做了一款 AI 动作迁移和动作控制的工具网站，可以让你将任何参考视频中的动作应用到任何角色或视频上。&lt;/p&gt;

&lt;p&gt;目标是让高质量的动作迁移和动作控制无需动作捕捉服、多重订阅、GPU 配置或复杂的节点式工作流程即可实现。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://img.way2solo.com/photo/seonhack/54956661-e779-4880-a4c2-e737d3f2f001.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/seonhack/2fed33ba-d752-4ff3-9418-d199d3a88ab1.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;其中我重点关注了以下几点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;保持身体动作的节奏和时机&lt;/li&gt;
&lt;li&gt;面部表情转移 + 对嘴&lt;/li&gt;
&lt;li&gt;快速舞蹈/编舞一致性&lt;/li&gt;
&lt;li&gt;场景感知替换（保留原始摄像机/背景）&lt;/li&gt;
&lt;li&gt;通过云推理完全运行在浏览器中&lt;/li&gt;
&lt;li&gt;队列管理&lt;/li&gt;
&lt;li&gt;提示重复使用&lt;/li&gt;
&lt;li&gt;稳定的出口&lt;/li&gt;
&lt;li&gt;优雅地应对失败的世代&lt;/li&gt;
&lt;li&gt;使输出足够重复，以便进行迭代创作&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;采用了以下技术：Next.js 15 + React 19 + TypeScript，UI 基于 Tailwind 4 + shadcn/Radix，目前部署在 cloudflare worker。&lt;/p&gt;

&lt;p&gt;目前仍然较难的地方：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;遮挡效果较强的场景&lt;/li&gt;
&lt;li&gt;多人互动一致性&lt;/li&gt;
&lt;li&gt;极快的摄像机运动场景&lt;/li&gt;
&lt;li&gt;长期时间连贯性问题&lt;/li&gt;
&lt;li&gt;部分型号手指精度问题&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;很想听听关于本项目的反馈，注册用户已赠送积分，可以测试。&lt;/p&gt;

&lt;p&gt;欢迎尝试：&lt;a href="https://motioncontrol.media/" rel="nofollow" target="_blank"&gt;https://motioncontrol.media/&lt;/a&gt;&lt;/p&gt;</description>
      <author>seonhack</author>
      <pubDate>Mon, 18 May 2026 11:09:03 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7350</link>
      <guid>http://www.w2solo.com/topics/7350</guid>
    </item>
    <item>
      <title>我的作品 AI YouTube Transcript：我把 YouTube transcript 搜索和导出这条小路径单独做成了一个页面</title>
      <description>&lt;p&gt;最近把一个很小但经常出现的需求单独做成了一个页面：AI YouTube Transcript。&lt;/p&gt;

&lt;p&gt;这个页面不是想做一个很大的视频 AI 工具集合，而是先把一条很具体的路径讲清楚：当我只需要从 YouTube 视频里找到一句话、确认时间点，并把 transcript 带到下一步时，能不能少一点来回切换。&lt;/p&gt;

&lt;p&gt;目前这页主要想把下面这条路径做顺：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;粘贴 YouTube 链接或视频 ID。&lt;/li&gt;
&lt;li&gt;选择可用语言。&lt;/li&gt;
&lt;li&gt;打开 transcript 文本。&lt;/li&gt;
&lt;li&gt;搜索需要确认的句子或关键词。&lt;/li&gt;
&lt;li&gt;通过时间戳回到视频里的对应位置。&lt;/li&gt;
&lt;li&gt;复制文本，或按后续用途导出 TXT、SRT、VTT。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我自己遇到这个需求时，通常不是为了重新看完整个视频，而是想快速找到某个片段、确认上下文，再把文本放进笔记、字幕、剪辑或资料整理流程里。所以 TXT、SRT、VTT 在这里不是 “多几个导出按钮”，而是对应不同的后续用法。&lt;/p&gt;

&lt;p&gt;现在这版我刻意做得比较克制，只保留最核心的能力。因为一旦想 “顺手多做一点”，页面就很容易从一个好理解的小工具，变成一个什么都想管、但什么都不够清楚的入口。&lt;/p&gt;

&lt;p&gt;边界也写得很明确：只有源视频暴露了可用的 subtitle 或 caption track，页面才可能加载 transcript；如果没有可用 track，就没有内容可搜索或导出，文本质量也取决于原始字幕轨道。&lt;/p&gt;

&lt;p&gt;页面在这里：
&lt;a href="https://aiyoutubetranscript.com/" rel="nofollow" target="_blank"&gt;https://aiyoutubetranscript.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;想听听大家建议：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;这种小工具页面，第一屏应该先强调 “搜索和导出 transcript”，还是先解释来源和限制？&lt;/li&gt;
&lt;li&gt;如果你要把视频内容转成笔记、字幕或素材，TXT/SRT/VTT 这几个出口够不够用？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://img.way2solo.com/photo/ethanjamescolez/248cbf08-6a5b-49e9-b085-7f008c1bc6e3.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>ethanjamescolez</author>
      <pubDate>Mon, 18 May 2026 01:14:47 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7349</link>
      <guid>http://www.w2solo.com/topics/7349</guid>
    </item>
    <item>
      <title>我让 AI 替我写 Git 提交信息，老板以为我每天工作 16 小时</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;公司要求提交信息必须规范：&lt;code&gt;feat: xxx&lt;/code&gt;、&lt;code&gt;fix: xxx&lt;/code&gt;、还要带 tapd 链接。我每次 git commit 都要憋五分钟，写出来的还是 “改了点东西”。后来我让 AI 替我写提交信息——diff 一丢进去，它自动生成规范格式，还帮我加上 “影响范围” 和 “为什么改”。老板看 Git 日志，说：“xxx 最近提交质量很高啊，你每天工作到几点？” 我：“正常六点下班。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;Git 提交信息这件事，说大不大，说小不小。小到你随便写个 “update”，不影响跑代码。大到代码回滚、发版生成 CHANGELOG、甚至背锅溯源时，一句清晰的 “fix: 修复订单金额计算溢出” 比 “改了一下” 值一万倍。&lt;/p&gt;

&lt;p&gt;但人都有惰性，尤其加班赶业务时，谁有空写小作文？AI 就不一样，它看 diff 快、理解上下文、还能按约定格式输出。今天我就教你用 AI 自动生成高质量的 commit message，顺便集成到 Git 钩子里，让你以后闭着眼敲 &lt;code&gt;git commit&lt;/code&gt; 就行。&lt;/p&gt;
&lt;h2 id="一、为什么你讨厌写 commit message？"&gt;一、为什么你讨厌写 commit message？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;没灵感&lt;/strong&gt;：改了好几个文件，不知怎么概括。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;规范记不住&lt;/strong&gt;：&lt;code&gt;feat&lt;/code&gt;/&lt;code&gt;fix&lt;/code&gt;/&lt;code&gt;docs&lt;/code&gt; 又要查表。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;懒&lt;/strong&gt;：反正也没人看，写个 “x” 交差。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但规范化的 message 真的有用：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;自动生成 &lt;code&gt;CHANGELOG.md&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git blame&lt;/code&gt; 时一眼看出某个改动的原因。&lt;/li&gt;
&lt;li&gt;同事 review PR 时不用猜你改了啥。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;既然人是懒的，就让 AI 当你的 “秘书”。&lt;/p&gt;
&lt;h2 id="二、AI 怎么写 commit message？"&gt;二、AI 怎么写 commit message？&lt;/h2&gt;
&lt;p&gt;核心思路：&lt;code&gt;git diff --staged&lt;/code&gt; 拿到本次变更的代码差异，丢给 AI，让它根据 Conventional Commits 规范生成消息。&lt;/p&gt;

&lt;p&gt;我给你写了一个脚本 &lt;code&gt;ai-commit&lt;/code&gt;（Python 版，你也可以用 Node）：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"你的key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 获取暂存区的 diff
&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_output&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"diff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--staged"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"没有暂存的变更，请先 git add"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"""
请根据以下 Git diff，按照 Conventional Commits 规范生成一条提交信息。
格式：&amp;lt;type&amp;gt;(&amp;lt;scope&amp;gt;): &amp;lt;subject&amp;gt;
其中 type 可选：feat, fix, docs, style, refactor, perf, test, chore
scope 可选（如组件名或模块名），subject 简短描述（不超过50字）。
如果变更涉及多个不相关改动，请拆成多条（用换行分隔）。
只输出提交信息，不要解释。

diff:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
"""&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"gpt-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把这个脚本保存为 &lt;code&gt;ai-commit&lt;/code&gt;，放到 PATH 里。以后你只需要：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
ai-commit   &lt;span class="c"&gt;# 它会打印出 AI 生成的 message&lt;/span&gt;
git commit &lt;span class="nt"&gt;-F&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;ai-commit&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;# 直接使用&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者再懒一点：用 &lt;code&gt;git commit -m "$(ai-commit)"&lt;/code&gt; 一行命令。&lt;/p&gt;
&lt;h2 id="三、集成到 Husky，每次 commit 自动调用"&gt;三、集成到 Husky，每次 commit 自动调用&lt;/h2&gt;
&lt;p&gt;你可以在 &lt;code&gt;.husky/prepare-commit-msg&lt;/code&gt; 里加入脚本，让 AI 自动填充编辑器：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# 自动生成 commit message 并预填到编辑器&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt; /dev/tty
&lt;span class="nv"&gt;AI_MSG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ai-commit&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AI_MSG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样你敲 &lt;code&gt;git commit&lt;/code&gt; 时，编辑框里已经有 AI 写好的草稿，你只需要检查一下，不满意就改，省得自己从头敲。&lt;/p&gt;
&lt;h2 id="四、真实案例：AI 写的 message 有多专业？"&gt;四、真实案例：AI 写的 message 有多专业？&lt;/h2&gt;
&lt;p&gt;某次我改了订单模块的一个 bug：之前只判断了 &lt;code&gt;user.discount&lt;/code&gt; 存在性，没判断类型，传了个字符串导致计算错误。AI 根据 diff 生成了：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fix(order): 优惠金额类型错误导致计算异常

- 增加 discount 字段的类型校验，确保为 number
- 添加单元测试覆盖字符串场景
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还帮我分了一行 body 说明？其实 Conventional Commits 允许 body，但我的提示词没要求。&lt;strong&gt;厉害的是&lt;/strong&gt;，它居然知道这是 “类型错误” 和 “计算异常”，而且自动给定了 &lt;code&gt;fix(order)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;另一个案例：我重构了一个函数，把 &lt;code&gt;if-else&lt;/code&gt; 改成了策略模式。AI 生成：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refactor(payment): 用策略模式替换多层条件分支

提升可扩展性，便于新增支付渠道
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一看就是老手写的。&lt;/p&gt;
&lt;h2 id="五、进阶：结合 TAPD/Jira，自动关联任务"&gt;五、进阶：结合 TAPD/Jira，自动关联任务&lt;/h2&gt;
&lt;p&gt;很多公司要求 commit 里带任务 ID。你可以在提示词里加入当前分支名（分支名通常含任务号）：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_output&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"branch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--show-current"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# 假设分支名为 feature/TAPD-1234
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后把 “请从分支名提取任务 ID，加入提交信息” 写进 prompt。AI 会生成类似：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feat(order): 添加满减优惠券

TAPD-1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连 hook 都不用改，全自动。&lt;/p&gt;
&lt;h2 id="六、局限性：AI 不是每次都完美"&gt;六、局限性：AI 不是每次都完美&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;巨大 diff&lt;/strong&gt;：超过上下文长度，AI 看不到全貌。可以只改最近几个文件，或者用 &lt;code&gt;git diff --cached --stat&lt;/code&gt; 先给 AI 看概览，再让它选择重点文件。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;多个无关改动&lt;/strong&gt;：AI 可能会合并成一条，而你应该拆成多条。这时候手动分两次 commit 就好。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;隐私&lt;/strong&gt;：代码 diff 会发给 OpenAI API，公司敏感项目慎用。可以本地跑开源模型（CodeLlama、DeepSeek Coder）替代。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="七、效果：我的 Git 日志变“教科书”"&gt;七、效果：我的 Git 日志变 “教科书”&lt;/h2&gt;
&lt;p&gt;之前我的日志：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fix
update
修改
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feat(user): 支持手机号登录
fix(cart): 修复商品数量为0时仍可结算的bug
perf(list): 虚拟滚动优化，长列表滚动帧率提升50%
docs(readme): 更新环境配置说明
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;老板进仓库看了一圈，专门在群里说：“xxx 的提交信息写得真规范，大家学习一下。” 我默默把 AI 脚本分享给了同事。现在全组都在用，老板还纳闷：怎么大家突然都变 “专业” 了？&lt;/p&gt;
&lt;h2 id="八、总结：把时间花在改 bug 上，不是写小作文"&gt;八、总结：把时间花在改 bug 上，不是写小作文&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AI 写 commit message，省时、规范、专业。&lt;/li&gt;
&lt;li&gt;集成到 Git 钩子，无感使用。&lt;/li&gt;
&lt;li&gt;支持从 diff 推断 type、scope、甚至业务含义。&lt;/li&gt;
&lt;li&gt;敏感项目换本地模型。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下一回你 &lt;code&gt;git commit&lt;/code&gt; 时，让 AI 替你写。你只需要 review 一下，不满意手动改两字。从此 Git 日志不再是 “xxx: 111”，而是真正的 “项目史书”。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;评论区聊聊：你见过最离谱的 commit message 是什么？&lt;/p&gt;</description>
      <author>193577746</author>
      <pubDate>Sun, 17 May 2026 12:36:40 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7348</link>
      <guid>http://www.w2solo.com/topics/7348</guid>
    </item>
    <item>
      <title>+86 用户专属：一个不用折腾代理、不用交 smsfee 的 TG 登录方案</title>
      <description>&lt;p&gt;最近想上 TG 跟进几个技术频道，结果第一步就被卡住了——注册/登录。&lt;/p&gt;

&lt;p&gt;+86 手机号，官方客户端登录时弹出一个叫"smsfee"的提示，按指引交了费用，短信验证码却像石沉大海。试了三四次，换了不同时间段，依旧收不到。搜了下社区讨论，发现这不是个案，运营商层面的拦截让不少人在这条路上折返。&lt;/p&gt;

&lt;p&gt;正当准备放弃时，朋友推荐了一款基于官方 12.5.1 版本二次编译的客户端。抱着试试看的心态用了几天，记录如下。&lt;/p&gt;

&lt;p&gt;登录环节&lt;/p&gt;

&lt;p&gt;最大的变化是绕过了 smsfee 的拦路虎。按照内置引导操作，从输入手机号到成功登录，整个过程大约两分钟，验证码到达速度正常。对于被官方注册流程劝退的人来说，这个入口友好很多。&lt;/p&gt;

&lt;p&gt;语言环境&lt;/p&gt;

&lt;p&gt;开箱即用的中文支持。界面菜单、群组列表、频道内容均显示为中文，没有出现常见的编码乱码或翻译生硬的问题。日常浏览不需要在脑子里做中英文切换，阅读效率明显提升。&lt;/p&gt;

&lt;p&gt;网络连接&lt;/p&gt;

&lt;p&gt;内置了网络层的调优方案。启动后自动完成连接，不需要手动填写代理参数，也不用额外配置工具。测试期间在不同网络环境下（家庭宽带、移动数据、公共 WiFi）均能稳定连上，没有出现频繁断线或消息延迟的情况。（说人话就是不用再翻墙了，大家也都知道最近国内很严！）&lt;/p&gt;

&lt;p&gt;功能完整性&lt;/p&gt;

&lt;p&gt;核心功能与官方版保持同步：私聊、群组讨论、频道订阅、消息推送、多账号切换均可正常使用。后台驻留稳定性不错，消息提醒及时，没有出现漏收通知的情况。界面交互逻辑熟悉，老用户上手零门槛。&lt;/p&gt;

&lt;p&gt;适用人群画像&lt;/p&gt;

&lt;p&gt;被官方注册流程卡住、急需使用 TG 的 +86 用户&lt;/p&gt;

&lt;p&gt;不想在代理配置和网络调试上花费时间的普通使用者&lt;/p&gt;

&lt;p&gt;偏好中文环境、希望开箱即用的日常浏览者&lt;/p&gt;

&lt;p&gt;客观总结&lt;/p&gt;

&lt;p&gt;这款客户端本质上是官方代码的本地化编译版本，在保留核心功能的前提下，针对性解决了 +86 用户常见的注册障碍和网络连接问题。它不是一个"增强版"，更像是一个"无障碍通道"——让原本被门槛挡住的人能够顺利进入，专注于内容本身。&lt;/p&gt;

&lt;p&gt;如果你也卡在登录这一步，或者只是想要一个省心的中文 TG 环境，可以留意评论区获取方式，按需自取即可。
&lt;img src="https://img.way2solo.com/photo/huo/f0cea13d-2f26-4e37-afae-a4a607badb91.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/huo/bf3a28ef-2170-40b0-89e8-5e1a54412c6b.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/huo/b708bff0-b5db-49ae-b331-627081af7ae4.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>huo</author>
      <pubDate>Sun, 17 May 2026 10:08:26 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7347</link>
      <guid>http://www.w2solo.com/topics/7347</guid>
    </item>
    <item>
      <title>Anthropic 内部数百个 Claude Code Skills，他们总结的这套方法值得看</title>
      <description>&lt;p&gt;大家好，我是 &lt;code&gt;Immerse&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;专注分享 &lt;code&gt;AI 玩法&lt;/code&gt;、&lt;code&gt;独立开发&lt;/code&gt;与&lt;code&gt;AI 出海&lt;/code&gt;的 AGI 实践者，更多干货欢迎关注公众号 &lt;a href="https://yaolifeng.com" rel="nofollow" target="_blank" title=""&gt;&lt;code&gt;#沉浸式AI&lt;/code&gt;&lt;/a&gt; 或访问 &lt;a href="https://yaolifeng.com" rel="nofollow" target="_blank" title=""&gt;&lt;code&gt;yaolifeng.com&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;个人 AI News 站更新啦：&lt;a href="https://news.yaolifeng.com" rel="nofollow" target="_blank" title=""&gt;&lt;code&gt;news.yaolifeng.com&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Anthropic 工程师 Thariq 最近在 X 上发了一篇长文，说他们公司内部现在有上百个 Skills 在跑，整理了一批踩坑经验。&lt;/p&gt;

&lt;p&gt;大多数人第一次接触 Claude Code Skills，以为它就是个 Markdown 文件——写点提示词，丢进去，完事。&lt;/p&gt;

&lt;p&gt;实际上 Skills 是文件夹，不是文件。里面可以放脚本、资产、数据、配置，整个目录结构都是上下文工程的一部分。&lt;/p&gt;

&lt;p&gt;这篇文章整理他们的方法论：Skills 的分类体系、写好一个 Skill 的关键技巧，以及团队内部怎么分发和管理。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/yaolifeng0629/oss/main/images/20260329182932457.jpg"&gt;&lt;/p&gt;
&lt;h2 id="9 种类型"&gt;9 种类型&lt;/h2&gt;
&lt;p&gt;他们把内部所有 Skills 整理了一遍，大概在这几个方向上&lt;/p&gt;
&lt;h3 id="1. 库和 API 参考"&gt;1. 库和 API 参考&lt;/h3&gt;
&lt;p&gt;用来解释如何正确用某个库、CLI 或 SDK。不管是内部自己的库，还是 Claude 经常用错的外部库，都可以做成这类 Skill。&lt;/p&gt;

&lt;p&gt;这类 Skills 通常带一个 references/ 文件夹，里面包含一个参考代码片段和踩坑清单。&lt;/p&gt;

&lt;p&gt;比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;billing-lib&lt;/code&gt; — 公司内部库，重点是边界情况和容易踩的坑&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;internal-platform-cli&lt;/code&gt; — 内部 CLI 工具的每个子命令，以及使用示例&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;frontend-design&lt;/code&gt; — 让 Claude 更好地理解你们的设计系统&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2. 产品验证"&gt;2. 产品验证&lt;/h3&gt;
&lt;p&gt;描述怎么测试或验证代码能不能正确运行。一般配合 Playwright、tmux 使用。&lt;/p&gt;

&lt;p&gt;Thariq 说得很直接：让一个工程师花一整周专门打磨验证 Skill，这个时间花得值&lt;/p&gt;

&lt;p&gt;比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;signup-flow-driver&lt;/code&gt; — 跑完整的注册流程（注册 → 邮件验证 → 引导），用 headless 浏览器，每步都有状态断言&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;checkout-verifier&lt;/code&gt; — 用 Stripe 测试卡驱动结账 UI，验证发票状态落地是否正确&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tmux-cli-driver&lt;/code&gt; — 交互式 CLI 测试，用于需要 TTY 的场景&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3. 数据获取和分析"&gt;3. 数据获取和分析&lt;/h3&gt;
&lt;p&gt;连接你的数据和监控系统。这类 Skill 会带上权限凭证的数据获取库、dashboard ID，以及常见的一些操作指南。&lt;/p&gt;

&lt;p&gt;比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;funnel-query&lt;/code&gt; — "注册 → 激活 → 付费"这条漏斗用哪些事件表 JOIN，canonical user_id 在哪张表&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cohort-compare&lt;/code&gt; — 对比两个用户群的留存或转化，标记统计显著的差异，链接到细分定义&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grafana&lt;/code&gt; — datasource UID、集群名称、问题到仪表盘的映射表&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4. 业务流程和团队自动化"&gt;4. 业务流程和团队自动化&lt;/h3&gt;
&lt;p&gt;把重复的工作流自动化成一个命令。指令本身通常比较简单，但可能依赖其他 Skill 或 MCP。&lt;/p&gt;

&lt;p&gt;可以把历史执行结果存进日志文件是个好习惯，可以帮 Claude 做前后对比，而不是每次从头开始。&lt;/p&gt;

&lt;p&gt;比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;standup-post&lt;/code&gt; — 汇总你的 ticket 系统、GitHub 动态和昨天的 Slack，生成只有增量变化的站会帖子&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create-ticket&lt;/code&gt; — 强制执行字段 schema（枚举值、必填字段），创建后自动触发后续流程（通知审核人、在 Slack 贴链接）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;weekly-recap&lt;/code&gt; — 合并的 PR + 关闭的 ticket + 部署记录，格式化成周报&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="5. 代码脚手架和模板"&gt;5. 代码脚手架和模板&lt;/h3&gt;
&lt;p&gt;为代码库里的某个功能生成框架代码。这类 Skill 特别适合那些有自然语言要求、纯代码覆盖不了的脚手架场景。&lt;/p&gt;

&lt;p&gt;比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;new-workflow&lt;/code&gt; — 按你们的注解规范，生成新 service/workflow/handler 的脚手架&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;new-migration&lt;/code&gt; — 数据库迁移文件模板，附带常见坑&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create-app&lt;/code&gt; — 新建内部应用，预置好你们的 auth、日志和部署配置&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="6. 代码质量和审查"&gt;6. 代码质量和审查&lt;/h3&gt;
&lt;p&gt;在团队内部执行代码质量标准，辅助代码审查。可以包含确定性脚本，也可以在 hooks 或 GitHub Actions 里自动触发。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;adversarial-review&lt;/code&gt; — 起一个新的 subagent 用全新视角批判代码，实现修复，迭代直到问题只剩鸡毛蒜皮&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;code-style&lt;/code&gt; — 强制执行代码风格，尤其是 Claude 默认不做的那些规范&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;testing-practices&lt;/code&gt; — 指导怎么写测试、测什么&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="7. CI/CD 和部署"&gt;7. CI/CD 和部署&lt;/h3&gt;
&lt;p&gt;帮助拉取、推送、部署代码，可能会引用其他 Skill 来收集数据。&lt;/p&gt;

&lt;p&gt;比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;babysit-pr&lt;/code&gt; — 监控 PR → 重试不稳定的 CI → 解决合并冲突 → 开启自动合并&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy-service&lt;/code&gt; — 构建 → 冒烟测试 → 灰度流量 + 错误率对比 → 自动回滚&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cherry-pick-prod&lt;/code&gt; — 隔离 worktree → cherry-pick → 解决冲突 → 附模板的 PR&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="8. Runbooks"&gt;8. Runbooks&lt;/h3&gt;
&lt;p&gt;接收一个问题症状（Slack 线程、告警、错误特征），它会执行多工具调查，输出结构化报告。&lt;/p&gt;

&lt;p&gt;比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;service-debugging&lt;/code&gt; — 把症状映射到工具和查询模式，针对流量最高的服务&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;oncall-runner&lt;/code&gt; — 拉取告警 → 检查常见嫌疑点 → 格式化调查结论&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;log-correlator&lt;/code&gt; — 给一个 request ID，从所有可能经过的系统里拉对应日志&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="9. 基础设施运维"&gt;9. 基础设施运维&lt;/h3&gt;
&lt;p&gt;执行日常维护和操作流程。破坏性操作放进 Skill 可以一层护栏，避免手滑导致不可逆的风险&lt;/p&gt;

&lt;p&gt;比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;orphans-cleanup&lt;/code&gt; — 找孤立的 pod/volume → 发 Slack → 等待期 → 用户确认 → 级联清理&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dependency-management&lt;/code&gt; — 你们组织的依赖审批流程&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cost-investigation&lt;/code&gt; — "为什么存储/出流量账单突然涨了"，定位到具体 bucket 和查询模式&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="写好一个 Skill 的关键技巧"&gt;写好一个 Skill 的关键技巧&lt;/h2&gt;

&lt;h3 id="不要放很常识的东西"&gt;不要放很常识的东西&lt;/h3&gt;
&lt;p&gt;Claude 已经懂很多编码知识，有自己的默认判断。如果你写的 Skill 主要是常识，它没什么价值。要把精力放在那些会把 Claude 推出默认思维的信息上。&lt;/p&gt;

&lt;p&gt;Anthropic 内部的 &lt;code&gt;frontend-design&lt;/code&gt; Skill 是个好例子——一个工程师跟用户迭代，专门调教 Claude 的设计品味，让它避开 Inter 字体和紫色渐变这类 AI 审美。这类东西 Claude 默认是不知道的。&lt;/p&gt;
&lt;h3 id="Gotchas 部分信噪比最高"&gt;Gotchas 部分信噪比最高&lt;/h3&gt;
&lt;p&gt;一个 Skill 里信噪比最高的部分就是 Gotchas。这里记录 Claude 实际使用你的 Skill 时踩过的坑——不是你预测它会踩的，是真的踩了的。边用边更新，遇到新的 edge case 就补进去。&lt;/p&gt;
&lt;h3 id="Skill 是文件夹，不是 md 文件"&gt;Skill 是文件夹，不是 md 文件&lt;/h3&gt;
&lt;p&gt;把整个文件系统当成一种上下文工程和渐进式披露的手段。在 SKILL.md 里告诉 Claude 有哪些文件，它会在合适的时候去读。&lt;/p&gt;

&lt;p&gt;最简单的做法是把详细内容拆到其他 md 文件里。比如函数签名和用法示例单独放到 &lt;code&gt;references/api.md&lt;/code&gt;，Claude 需要的时候再去读，而不是一开始就全部加载进上下文。&lt;/p&gt;

&lt;p&gt;你还可以这样组织：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;assets/&lt;/code&gt; 放模板文件，Claude 要生成对应格式的输出时直接复制用&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/&lt;/code&gt; 放可以直接调用的工具脚本&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;references/&lt;/code&gt; 放参考代码片段和文档&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="给 Claude 信息，不要给 Claude 剧本"&gt;给 Claude 信息，不要给 Claude 剧本&lt;/h3&gt;
&lt;p&gt;因为 Skill 是高度复用的，过于具体的指令反而会出问题。给 Claude 它需要的信息，但给它适应当前情况的空间。不要把每一步都写死，把判断权留给 Claude。&lt;/p&gt;
&lt;h3 id="Setup 环节要想清楚"&gt;Setup 环节要想清楚&lt;/h3&gt;
&lt;p&gt;有些 Skill 需要用户提供上下文才能工作&lt;/p&gt;

&lt;p&gt;最常见模式：配置信息存在 Skill 目录下的 &lt;code&gt;config.json&lt;/code&gt; 里，如果配置不存在，Agent 去问用户。要给出结构化的多选题，可以在 Skill 里指示使用 &lt;code&gt;AskUserQuestion&lt;/code&gt; 工具。&lt;/p&gt;
&lt;h3 id="description 字段是给模型看的触发条件"&gt;description 字段是给模型看的触发条件&lt;/h3&gt;
&lt;p&gt;Claude Code 启动时会给每个 Skill 建一个带 description 的列表，扫这个列表来判断「有没有对应这个请求的 Skill」。所以 description 要写清楚触发时机，而不是 Skill 功能的摘要。&lt;/p&gt;
&lt;h3 id="Skills 可以有记忆"&gt;Skills 可以有记忆&lt;/h3&gt;
&lt;p&gt;Skill 里可以包含一种记忆，通过在 Skill 目录里存数据实现。从简单的 append-only 文本日志、JSON 文件，到复杂的 SQLite 数据库都可以。&lt;/p&gt;

&lt;p&gt;比如 &lt;code&gt;standup-post&lt;/code&gt; Skill 维护一个 &lt;code&gt;standups.log&lt;/code&gt;，记录每次写的内容，下次运行时 Claude 读自己的历史，知道哪些是新变化。&lt;/p&gt;

&lt;p&gt;注意：存在 Skill 目录里的数据升级时可能被删掉。持久数据应该存到 &lt;code&gt;${CLAUDE_PLUGIN_DATA}&lt;/code&gt;，这是每个插件独立的稳定存储路径。&lt;/p&gt;
&lt;h3 id="在 Skills 里放脚本"&gt;在 Skills 里放脚本&lt;/h3&gt;
&lt;p&gt;给 Claude 代码比给指令有效得多。有了脚本和库，Claude 就能把时间花在组合和决策上，而不是每次从头重建样板代码。&lt;/p&gt;

&lt;p&gt;比如数据科学类 Skill 里放一组从事件源取数的 helper 函数，Claude 就可以按需动态生成脚本来做进一步分析，而不是每次自己写逻辑。&lt;/p&gt;
&lt;h3 id="按需 Hooks"&gt;按需 Hooks&lt;/h3&gt;
&lt;p&gt;Skill 可以包含只在 Skill 被调用时激活的 hooks，持续到这个 session 结束。这适合那些场景特定、不想一直开着的强约束 hook。&lt;/p&gt;

&lt;p&gt;两个典型例子：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/careful&lt;/code&gt; — 通过 PreToolUse matcher 拦截 &lt;code&gt;rm -rf&lt;/code&gt;、&lt;code&gt;DROP TABLE&lt;/code&gt;、force-push、&lt;code&gt;kubectl delete&lt;/code&gt;。只在你知道自己要动 prod 的时候才需要，常开会让人崩溃&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/freeze&lt;/code&gt; — 拦截所有不在指定目录里的 Edit/Write。调试时很有用："我想加日志，但我总是顺手把别的东西改了"&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="怎么在团队里分发 Skills"&gt;怎么在团队里分发 Skills&lt;/h2&gt;
&lt;p&gt;分发方式取决于团队规模：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;小团队&lt;/strong&gt;：把 Skills 提交进代码仓库（&lt;code&gt;./.claude/skills&lt;/code&gt; 目录）。团队人不多、仓库也不多的情况下，这样最简单直接。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;大团队&lt;/strong&gt;：搭内部插件市场，按需安装。这是因为每个提交进仓库的 Skill 都会给模型的上下文增加一点点体积，规模大了之后就不可忽视了。内部市场让每个人自己决定装哪些。&lt;/p&gt;
&lt;h3 id="Skills 互相引用"&gt;Skills 互相引用&lt;/h3&gt;
&lt;p&gt;如果你有一个 Skill 要依赖另一个 Skill，直接在指令里引用另一个 Skill 的名字就行，Claude 会在它已安装的情况下调用它。这个依赖管理目前还没有原生的 Skill 系统支持，但这种方式能跑通。&lt;/p&gt;
&lt;h3 id="Skill 使用情况"&gt;Skill 使用情况&lt;/h3&gt;
&lt;p&gt;Anthropic 用 PreToolUse hook 记录公司内部的 Skill 使用情况。这样能看到哪些 Skill 频繁被用，哪些触发率低于预期——后者意味着 description 没写好，或者这个 Skill 根本没解决真实需求。&lt;/p&gt;

&lt;p&gt;发现 Skill 很少被触发，先检查 description 是不是清楚地描述了触发场景。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;大部分 Skill 在 Anthropic 内部也是从几行指令、一个 Gotcha 开始的，遇到新 edge case 就补进去，慢慢变好的。挑一件你每天重复干的事，写一个最简版，先跑通。&lt;/p&gt;</description>
      <author>Immerse</author>
      <pubDate>Sat, 16 May 2026 15:42:16 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7346</link>
      <guid>http://www.w2solo.com/topics/7346</guid>
    </item>
    <item>
      <title>全自动产品开发，没有设计师也能自己设计产品。电商福音</title>
      <description>&lt;p&gt;这个网站  解决了想做新产品，但又不懂设计的难题，没有设计师公司的福音！&lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;/p&gt;

&lt;p&gt;这个网站其实很简单，就是为了帮那些有产品设计思维，但不会使用专业绘图软件的老板，产品经理，个人。解决产品开发的难题。
&lt;img src="https://img.way2solo.com/photo/yea032/237e61ac-ef45-4105-9af4-cac1cacc6f9c.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;每一个做产品卖产品的公司，包括一些小店，个人，都会有开发新品的想法，尤其是一些加工厂，电商公司；都会希望在每一个阶段上新品。有时候老板自己本人或者产品经理，可能也有自己的产品开发想法，但是自己又不懂怎么使用绘图软件，然而，身边又没有设计师；若专门去外面找一个设计师，成本高，还不一定能找到合适的。这个网站就是解决这个问题的。
&lt;img src="https://img.way2solo.com/photo/yea032/dbf845e0-1210-425b-ba04-c2a1da4d8f5b.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;人人都可以在上面开发自己的产品，它并不是换材质，迁移，换颜色那么简单，而是可以帮你创作出一个新的产品；虽然创作出来的图不一定能直接生产使用，但是能给你发散思维，让你的想法直观化，看得见，为你开发产品节约时间和脑力！&lt;/p&gt;

&lt;p&gt;而且网站当前还可以免费入驻设计师和供应商！当你生成的图需要专业人士调整时，就找网站内部的设计师调整；当你想打样，生成的时候，就找网站内部的供应商。也就是说，从灵感开发到落地，都包含在内了。
&lt;img src="https://img.way2solo.com/photo/yea032/fdf29097-1e0b-4f74-9fba-9a8e7e6a1289.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;本站用法很简单，只需要上传一张你想开发的对标产品图，如果你有自己想法，就把想法写在图片中；如果你想结合其他图的特征设计，就上传其他参考图；网站会自动思考，结合你所上传的所有参考图，用它自己的思维帮你创作出一个原创新品。全程不用设置，不用写提示词，会用鼠标就可以。
&lt;img src="https://img.way2solo.com/photo/yea032/55c8920e-d2c0-40b4-8c92-4d0f20db4f6b.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/yea032/b9df677f-b4e2-4908-a521-13d1d6c8a26c.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;你感觉这个网站怎样？能帮到你吗？ 网址：&lt;a href="https://www.takingai.cn/" rel="nofollow" target="_blank"&gt;https://www.takingai.cn/&lt;/a&gt;&lt;/p&gt;</description>
      <author>yea032</author>
      <pubDate>Sat, 16 May 2026 14:16:07 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7345</link>
      <guid>http://www.w2solo.com/topics/7345</guid>
    </item>
    <item>
      <title>AI Rapper 说唱音乐生成器</title>
      <description>&lt;p&gt;写了个在线说唱音乐的项目，有兴趣的可以试试看  &lt;a href="https://rapper.im/" rel="nofollow" target="_blank"&gt;https://rapper.im/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;可以用来表达观点，或者 作为短视频背景声音啥的&lt;/p&gt;</description>
      <author>finekewei</author>
      <pubDate>Sat, 16 May 2026 13:35:26 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7344</link>
      <guid>http://www.w2solo.com/topics/7344</guid>
    </item>
    <item>
      <title>这网站有点意思。只要一张对标产品图，就能设计出新的产品，还不用提示词。</title>
      <description>&lt;p&gt;这个网站  解决了想做新产品，但又不懂设计的难题，没有设计师公司的福音！&lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;/p&gt;

&lt;p&gt;这个网站其实很简单，就是为了帮那些有产品设计思维，但不会使用专业绘图软件的老板，产品经理，个人。解决产品开发的难题。
&lt;img src="https://img.way2solo.com/photo/yea032/237e61ac-ef45-4105-9af4-cac1cacc6f9c.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;每一个做产品卖产品的公司，包括一些小店，个人，都会有开发新品的想法，尤其是一些加工厂，电商公司；都会希望在每一个阶段上新品。有时候老板自己本人或者产品经理，可能也有自己的产品开发想法，但是自己又不懂怎么使用绘图软件，然而，身边又没有设计师；若专门去外面找一个设计师，成本高，还不一定能找到合适的。这个网站就是解决这个问题的。
&lt;img src="https://img.way2solo.com/photo/yea032/dbf845e0-1210-425b-ba04-c2a1da4d8f5b.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;人人都可以在上面开发自己的产品，它并不是换材质，迁移，换颜色那么简单，而是可以帮你创作出一个新的产品；虽然创作出来的图不一定能直接生产使用，但是能给你发散思维，让你的想法直观化，看得见，为你开发产品节约时间和脑力！&lt;/p&gt;

&lt;p&gt;而且网站当前还可以免费入驻设计师和供应商！当你生成的图需要专业人士调整时，就找网站内部的设计师调整；当你想打样，生成的时候，就找网站内部的供应商。也就是说，从灵感开发到落地，都包含在内了。
&lt;img src="https://img.way2solo.com/photo/yea032/fdf29097-1e0b-4f74-9fba-9a8e7e6a1289.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;本站用法很简单，只需要上传一张你想开发的对标产品图，如果你有自己想法，就把想法写在图片中；如果你想结合其他图的特征设计，就上传其他参考图；网站会自动思考，结合你所上传的所有参考图，用它自己的思维帮你创作出一个原创新品。全程不用设置，不用写提示词，会用鼠标就可以。
&lt;img src="https://img.way2solo.com/photo/yea032/55c8920e-d2c0-40b4-8c92-4d0f20db4f6b.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/yea032/b9df677f-b4e2-4908-a521-13d1d6c8a26c.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;你感觉这个网站怎样？能帮到你吗？ 网址：&lt;a href="https://www.takingai.cn/" rel="nofollow" target="_blank"&gt;https://www.takingai.cn/&lt;/a&gt;&lt;/p&gt;</description>
      <author>yea032</author>
      <pubDate>Sat, 16 May 2026 11:24:06 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7343</link>
      <guid>http://www.w2solo.com/topics/7343</guid>
    </item>
    <item>
      <title>一款简约，纯粹的 AI 工具导航站</title>
      <description>&lt;p&gt;这是一个专门收集和整理 AI 工具的网站。现在市面上的 AI 工具越来越多，ChatGPT、Midjourney、Stable Diffusion 这些名字你可能都听过，但真要找一个适合自己的工具，往往要翻半天。&lt;/p&gt;

&lt;p&gt;这个网站就是帮你解决这个问题的——它把各种 AI 工具按类别整理好，你打开网站就能看到，点一下就能用。&lt;/p&gt;

&lt;p&gt;网站首页按分类展示工具，比如 AI 写作、AI 绘画、AI 编程、AI 视频等等。每个分类下面列出对应的工具，有工具名称、简介和图标，一目了然。看到感兴趣的工具，点进去就能看到详细介绍，包括这个工具能干什么、有什么特色、适合什么场景、常见问题怎么解决。&lt;/p&gt;

&lt;p&gt;地址：&lt;a href="https://ai.fly63.com" rel="nofollow" target="_blank"&gt;https://ai.fly63.com&lt;/a&gt;&lt;/p&gt;</description>
      <author>fly63</author>
      <pubDate>Sat, 16 May 2026 11:21:25 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7342</link>
      <guid>http://www.w2solo.com/topics/7342</guid>
    </item>
    <item>
      <title>Gemini cli 登录异常解法</title>
      <description>&lt;p&gt;事情是这样的。&lt;/p&gt;

&lt;p&gt;今天下午，有个项目要用到 Gemini CLI，我想着那就登一下吧，反正之前也用过。&lt;/p&gt;

&lt;p&gt;结果终端输完命令，点完授权，跳完浏览器，回到终端。&lt;/p&gt;

&lt;p&gt;就给我看这个。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://gitee.com/da-qiang-classmate/typora/raw/master/image/20260514173105947.webp" title="" alt="844"&gt;&lt;/p&gt;
&lt;h3 id="全网都在教你调代理"&gt;全网都在教你调代理&lt;/h3&gt;
&lt;p&gt;嗯？&lt;/p&gt;

&lt;p&gt;什么情况？&lt;/p&gt;

&lt;p&gt;我以为是网络波动，重试了两次，还是一样。&lt;/p&gt;

&lt;p&gt;于是我去搜了一下，好家伙，一搜才发现，这个问题已经爆了快一个月了。整个中文互联网搜 gemini cli 登不上，出来的帖子能翻十几页，从 CSDN 到知乎到 GitHub Issue，所有人都在骂。&lt;/p&gt;

&lt;p&gt;然后你猜所有人给出的解决方案是什么？&lt;/p&gt;

&lt;p&gt;清一色，检查代理。&lt;/p&gt;

&lt;p&gt;什么检查环境变量啊，什么给 PowerShell 单独设置 WinHTTP 代理啊，什么用 proxychains 啊，还有说要在环境变量里配置谷歌云项目 ID 的。&lt;/p&gt;

&lt;p&gt;我当时还真信了。&lt;/p&gt;

&lt;p&gt;于是我照着网上的教程，一步一步来。&lt;/p&gt;

&lt;p&gt;先在 PowerShell 里敲 &lt;code&gt;netsh winhttp show proxy&lt;/code&gt;，看看代理是不是生效。&lt;/p&gt;

&lt;p&gt;显示正常。&lt;/p&gt;

&lt;p&gt;不放心，又手动设了一遍 &lt;code&gt;netsh winhttp set proxy 127.0.0.1:7890&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;然后再去登。&lt;/p&gt;

&lt;p&gt;还是失败。&lt;/p&gt;

&lt;p&gt;我当时就有点懵了。&lt;/p&gt;

&lt;p&gt;我甚至把代理关了又开，开了又关，节点换了好几个，从香港换到新加坡换到日本，甚至试了美国原生家宽。&lt;/p&gt;

&lt;p&gt;没用，就是登不上。&lt;/p&gt;

&lt;p&gt;。。。&lt;/p&gt;
&lt;h3 id="真正的原因，被所有人忽略了"&gt;真正的原因，被所有人忽略了&lt;/h3&gt;
&lt;p&gt;这个时候我突然想起一件事。&lt;/p&gt;

&lt;p&gt;大概两周前，我发现 Google AI Studio 打不开了，浏览器里直接 403，当时以为是谷歌又封了什么区域，也没在意。&lt;/p&gt;

&lt;p&gt;这两件事会不会有什么联系？&lt;/p&gt;

&lt;p&gt;我于是去搜「Google AI Studio 403」，出来的结果让我一下子就反应过来了。&lt;/p&gt;

&lt;p&gt;原来，谷歌从上个月开始，悄悄给所有用户发了一封邮件，要求进行年龄验证。&lt;/p&gt;

&lt;p&gt;你没看错，年龄验证。&lt;/p&gt;

&lt;p&gt;因为之前注册谷歌账号的时候，那个年龄你随便填的，谷歌根本不会真的去验证。你说你 18 你就 18，你说你 80 你就 80。&lt;/p&gt;

&lt;p&gt;但现在不行了。&lt;/p&gt;

&lt;p&gt;谷歌说，不行，我现在要开始认真了。如果你不做年龄验证，那所有跟 AI 相关的服务，你就别想用了。&lt;/p&gt;

&lt;p&gt;包括 AI Studio，也包括 Gemini CLI。&lt;/p&gt;

&lt;p&gt;我靠。&lt;/p&gt;

&lt;p&gt;我突然想起来，那封邮件我确实收到过，当时扫了一眼标题，觉得又是谷歌什么乱七八糟的通知，直接就给删了。&lt;/p&gt;

&lt;p&gt;谁能想到，就这么个破事，能把我卡一下午？&lt;/p&gt;

&lt;p&gt;于是我赶紧去把年龄验证做了，链接放这里，你们遇到同样问题的也可以直接去。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://myaccount.google.com/age-verification" rel="nofollow" target="_blank"&gt;https://myaccount.google.com/age-verification&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://gitee.com/da-qiang-classmate/typora/raw/master/image/20260514203756290.webp" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;验证完，我先去打开 AI Studio，果然能进了。&lt;/p&gt;

&lt;p&gt;然后回到终端，再敲一遍 gemini 登录。&lt;/p&gt;

&lt;p&gt;授权，跳转，确认。&lt;/p&gt;

&lt;p&gt;成了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://gitee.com/da-qiang-classmate/typora/raw/master/image/20260514205006826.webp" title="" alt="842"&gt;&lt;/p&gt;
&lt;h3 id="写在最后"&gt;写在最后&lt;/h3&gt;
&lt;p&gt;我当时坐在屏幕前，真的就是一声叹息。&lt;/p&gt;

&lt;p&gt;你说这叫什么事啊。&lt;/p&gt;

&lt;p&gt;整整一个月，整个中文互联网所有人都在教你怎么调代理，所有人都在骂谷歌的反人类网络设置。&lt;/p&gt;

&lt;p&gt;结果真正的原因，是你没去填那个年龄验证。&lt;/p&gt;

&lt;p&gt;没有任何一篇文章提到这件事。&lt;/p&gt;

&lt;p&gt;所有人都在错误的方向上，互相抄来抄去，把一个根本不存在的问题，越描越复杂。&lt;/p&gt;

&lt;p&gt;最后甚至形成了一种集体幻觉，好像真的是代理的问题，好像真的只要你多换几个节点，多试几次，总能成功。&lt;/p&gt;

&lt;p&gt;我后来在想，为什么会这样？&lt;/p&gt;

&lt;p&gt;大概是因为，我们已经太习惯「谷歌的服务出问题，那肯定是代理的锅」这个条件反射了。以至于当真正的问题出在别的地方时，我们甚至连想都不会往那个方向去想。&lt;/p&gt;

&lt;p&gt;我们只会一遍又一遍地，在同一个错误的地方，用同一种错误的方法，试图得到一个正确的结果。&lt;/p&gt;

&lt;p&gt;这跟现在很多人用 AI 的状态，是不是也有点像？&lt;/p&gt;

&lt;p&gt;遇到问题，第一反应不是去想「这个问题的本质是什么」，而是去搜「别人是怎么解决的」，然后照着抄一遍。抄来抄去，所有人都在同一个坑里打转，就是没人愿意停下来，多问一句，真的是这样吗？&lt;/p&gt;

&lt;p&gt;哦对了，如果你现在去谷歌搜「gemini cli 登录不上」，排在前几的那几篇文章，还是在教你怎么设置代理。&lt;/p&gt;

&lt;p&gt;没有人更新。&lt;/p&gt;

&lt;p&gt;没有人发现真正的原因。&lt;/p&gt;

&lt;p&gt;所有人都还在那个坑里。&lt;/p&gt;

&lt;p&gt;信息差啊，朋友们。&lt;/p&gt;

&lt;p&gt;有时候，你跟正确答案之间的距离，不是你不够努力，也不是你技术不够好。&lt;/p&gt;

&lt;p&gt;就只是，没有人告诉你，真正的问题是什么。&lt;/p&gt;

&lt;p&gt;以上，既然看到这里了，如果觉得不错，随手点个赞、在看、转发三连吧，如果想第一时间收到推送，也可以给我个星标⭐～&lt;/p&gt;

&lt;p&gt;谢谢你看我的文章，我们，下次再见。&lt;/p&gt;

&lt;p&gt;公众号：大强同学&lt;/p&gt;</description>
      <author>sphinx30</author>
      <pubDate>Fri, 15 May 2026 23:05:25 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7341</link>
      <guid>http://www.w2solo.com/topics/7341</guid>
    </item>
    <item>
      <title>我让 AI 当了 24 小时全年无休的 “毒舌考官”</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;公司开了个仓库，前端团队从 3 人扩到 12 人，我定规矩：所有 PR 至少 1 个 Review 才能合。好的，大家开始互相送 “LGTM ⭐” 了——一眼都没看，点完就跑。线上挂的时候谁也背锅，群消息全是 “不是我”。我一气之下拉了 AI 进来当永久 Code Reviewer，0 薪水 7x24 小时在线，什么混子、什么歪代码，全部兜住。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;PR 审查这件事，经历过的都懂：平时没人看，一出事全是 “我早说了”。你认真看了 500 行 diff 给出了 3 页修改意见，人家回复一个 “done”，你都不知道改没改对。&lt;/p&gt;

&lt;p&gt;Code Review 的最大矛盾是什么？&lt;strong&gt;要快，就水；要细，就慢&lt;/strong&gt;。AI 时代，你猜怎么着？这活儿最适合交给机器人。不需要情商，直接喷；不需要开会，秒回；不需要记住项目规范，每次都按最新标准来。&lt;/p&gt;

&lt;p&gt;今天我来实操：用 AI 在自己的 PR 里自动跑 Code Review，准确到几行代码，还能直接输出 “P0 严重”、“P1 重要”、“P2 建议” 的结构化报告。准备接受 AI 对你代码的无差别吐槽了吗？&lt;/p&gt;
&lt;h2 id="一、AI 当 reviewer 会“胡说八道”吗？"&gt;一、AI 当 reviewer 会 “胡说八道” 吗？&lt;/h2&gt;
&lt;p&gt;你先不用管它会胡说八道。你只要让它遵循的原则是 &lt;strong&gt;“如果这条建议可能不对，就闭嘴”&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;打个比方，你让人工 review：“这个 useEffect 漏了清理函数” → 这是对的。“你这里最好改用 useMemo” → 有时是对的，但也可能是在显摆。&lt;/p&gt;

&lt;p&gt;AI 也一样，你调校它的方式不是 “提示词”，而是&lt;strong&gt;给它喂规则&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;来看看我用了大半年的一套 AI Code Review 命令行工具能 “喷” 什么：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;security&lt;/strong&gt;：检测登录态是否在前端 localStorage 明文存密码、有没有 XSS 注入风险、SQL 注入隐患。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;performance&lt;/strong&gt;：看你有没有在 React 组件里直接写 &lt;code&gt;const style = { margin: 10 }&lt;/code&gt;（每次渲染新建对象）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;accessibility&lt;/strong&gt;：img 缺 alt、button 缺类型、颜色对比度稀烂。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;code quality&lt;/strong&gt;：文件超过 500 行给你标黄、catch 了错误只 &lt;code&gt;console.log&lt;/code&gt;（等于没 catch）、变量名拼写错误。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这套逻辑用下来，我的体感：&lt;strong&gt;AI 发现问题的速度是人类的 20 倍以上，遗漏率比人类 reviewer 低得多，而且从不得罪人&lt;/strong&gt;——因为它不带 “你写得好烂” 的情绪，只有纯技术判断。&lt;/p&gt;
&lt;h2 id="二、一行命令立刻拥有"&gt;二、一行命令立刻拥有&lt;/h2&gt;
&lt;p&gt;我最近一直在用一个叫 &lt;strong&gt;ai-review-pipeline&lt;/strong&gt; 的工具，它的理念很好：“AI 审查 AI 写的代码”——现在是 AI 辅助编程时代了，写得快但容易藏 bug，我正好需要这种【7x24 小时兜底】&lt;/p&gt;

&lt;p&gt;这个工具的特点是用 AI 自动 review + 生成测试 + 自动修复。你就无脑给它一个文件或目录，它出来的是&lt;strong&gt;评分 + 问题清单 + 修复建议 + 测试用例&lt;/strong&gt;。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ai-review-pipeline &lt;span class="nt"&gt;--file&lt;/span&gt; src/ &lt;span class="nt"&gt;--full&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它的流程很清晰：先跑确定性规则检查（比如 ESLint 已有的问题一次性过滤掉，不让 AI 重复废话），然后 AI 带着你配置好的团队规范去读 diff，每发现一个问题就给等级、给修复建议、给代码示例。如果你加 &lt;code&gt;--fix&lt;/code&gt; 参数，它甚至会尝试&lt;strong&gt;自动修&lt;/strong&gt;然后重新 review。&lt;/p&gt;

&lt;p&gt;你说：“万一自动修复改错了逻辑呢？” 错不了，AI 只敢改那些确定性高的低质量代码（多余空格、未使用的变量、非空断言错误等）。&lt;strong&gt;业务逻辑？它先绕开，你合并之前自己把关。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;而且支持绝大部分场景（后端 Java, Go, Python, Rust 也能喷）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;前端代码 review（React / Vue / TSX、CSS、a11y）&lt;/li&gt;
&lt;li&gt;backend-code-review（API 设计、数据库、并发安全）&lt;/li&gt;
&lt;li&gt;自动修复（默认只修格式、未使用的变量、确定的 lint 类问题，不敢动业务逻辑）&lt;/li&gt;
&lt;li&gt;Multi-LLM（OpenAI、Claude、DeepSeek、Gemini、Ollama 都能跑）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="三、调 AI 的“毒舌参数”，让它喷得更专业"&gt;三、调 AI 的 “毒舌参数”，让它喷得更专业&lt;/h2&gt;
&lt;p&gt;我用过 Grok、Claude 和 DeepSeek，相对用得最深的是 &lt;strong&gt;DeepSeek&lt;/strong&gt; 和 &lt;strong&gt;OpenAI&lt;/strong&gt;。调教参数也很简单：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 预设深度更狠一点，让它给你抓出 P0~P3 等级的分类&lt;/span&gt;
ai-review config &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;strictness&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;high
ai-review config &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;outputFormat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;structured
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AI 的审查结果可以直接挂在 GitHub PR 里当 Comment，每条对应具体行数和问题等级。一旦 AI 给了 &lt;strong&gt;P0（严重）&lt;/strong&gt; ，CI 直接 block 合并，你必须解决。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：如果你的项目有特别明确的 “业务规范”，你可以写到 &lt;code&gt;.ai-pipeline.json&lt;/code&gt; 配置文件里，例如 &lt;code&gt;“禁止引入 moment.js”&lt;/code&gt;、&lt;code&gt;“禁止在循环中使用 await”&lt;/code&gt;，AI 会把这部分自动纳入审查范畴，跟内置规则并排跑。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="四、这套 AI Code Review 还解决了哪些痛点？"&gt;四、这套 AI Code Review 还解决了哪些痛点？&lt;/h2&gt;&lt;h3 id="1. 跨语言能力 + 敏感信息排查"&gt;1. 跨语言能力 + 敏感信息排查&lt;/h3&gt;
&lt;p&gt;检查出来的问题几乎横跨你代码库所有位置：泄漏的 API endpoint、调试信息意外留到生产、图片 dead link 甚至无障碍视觉回归。&lt;/p&gt;

&lt;p&gt;它有一个特殊技巧：&lt;strong&gt;让多个 LLM 并行审查、互相辩论&lt;/strong&gt;，然后一个 “法官 agent” 给出最终裁决。不同模型能逮到不同 bug，通过共识机制滤掉噪音。&lt;/p&gt;
&lt;h3 id="2. CI 门禁，上线之前最后一道防线"&gt;2. CI 门禁，上线之前最后一道防线&lt;/h3&gt;
&lt;p&gt;你可以把 &lt;code&gt;ai-review-pipeline&lt;/code&gt; 挂在 GitHub Actions 上，每次 PR 自动执行。如果评分低于阈值（比如 80 分），CI 直接标红，连人都不用到场干预。&lt;/p&gt;

&lt;p&gt;某互联网团队落地类似系统后，&lt;strong&gt;代码审查周期缩短了 65%，基础性缺陷拦截率提升了 82%&lt;/strong&gt;。这个数据就是 AI review 正面效果的最好说明。&lt;/p&gt;
&lt;h2 id="五、毒舌吐槽大会：AI 喷过的那些人类代码"&gt;五、毒舌吐槽大会：AI 喷过的那些人类代码&lt;/h2&gt;
&lt;p&gt;我随便举几个真实案例，括号里是 AI 原话（节选）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;案例 1&lt;/strong&gt;：&lt;code&gt;setTimeout(() =&amp;gt; { ... }, 0)&lt;/code&gt; 用来模拟 “异步一下”。AI 喷：“用 setTimeout 0 是反模式，你应该用 Promise.resolve().then() 或者 queueMicrotask。——另外，你连错误处理都没有？”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;案例 2&lt;/strong&gt;：自己封装了一个 &lt;code&gt;request&lt;/code&gt; 函数，每个请求都 &lt;code&gt;console.log&lt;/code&gt; 全部响应体数据。AI 喷：“你在生产环境把整个 API 响应结构暴露到控制台，包含了敏感字段。P0 严重。建议只 log trace id。”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;案例 3&lt;/strong&gt;：&lt;code&gt;&amp;lt;img src={userInput} /&amp;gt;&lt;/code&gt;。AI 喷：“你直接把用户输入当 src？明天你的网站就变成菠菜广告基地。用 DOMPurify 或者干脆用 background-image 托管。”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这套 AI 让我晚上睡得踏实。自从它上线，我再也没有过 “线上又崩了，测试也没测出来” 的半夜来电。&lt;/p&gt;
&lt;h2 id="六、总结：AI 帮你 review 代码不是替代人，而是让你做更高价值的事"&gt;六、总结：AI 帮你 review 代码不是替代人，而是让你做更高价值的事&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AI 擅长一致性检查、边界检测、安全底网，还能给出可落地的修复示例。人类 reviewer 只需要关注架构、业务语义、用户体验。&lt;/li&gt;
&lt;li&gt;成本几乎为零，你只需要提供几个免费 API（Ollama、Groq 或各家入门额度）+ 一条 &lt;code&gt;npx&lt;/code&gt; 命令。&lt;/li&gt;
&lt;li&gt;未来方向：不只是 review，还开始蔓延到&lt;strong&gt;自动修复 + 测试生成 + 知识沉淀&lt;/strong&gt;，AI 在 PR 里已经可以替你完成 80% 的 “找茬” 工作。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我的建议：&lt;strong&gt;今天就拉一个这样的工具进你的私有仓库和团队&lt;/strong&gt;。领导不批预算？不用批，无门槛，立刻跑起来。等下次有人 PR 又是 “空代码 + 俩表情包” 来糊弄时，AI 会替你第一时间上去喷它：“你写的代码，想让我怎么 review？请自重点。”&lt;/p&gt;</description>
      <author>193577746</author>
      <pubDate>Fri, 15 May 2026 19:45:35 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7340</link>
      <guid>http://www.w2solo.com/topics/7340</guid>
    </item>
    <item>
      <title>给 Shipstry 加了个外链提交追踪功能，解决独立开发者分散提交的痛点</title>
      <description>&lt;p&gt;做独立开发的朋友应该都有这个经历：产品上线后要往各种目录网站提交，Product Hunt 、Reddit 、GitHub Pages 、各种 AI 工具目录……&lt;/p&gt;

&lt;p&gt;然后很快就失控了：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;哪些提交了、哪些没提交，记不清&lt;/li&gt;
&lt;li&gt;哪些通过了、哪些被拒了，没追踪&lt;/li&gt;
&lt;li&gt;靠 Excel 或者 Notion 记，就是手工活多一点&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以我给自己的产品 Shipstry 做了个 Backlink Submissions 功能，专门解决这个问题。 主要功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discover：内置 800+ 目录，按 DR（ Domain Rating ）排序，GitHub DR96 、Gravatar DR97 、Reddit DR95 这些都有&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Queue：看中哪个目录直接加队列，不用手动复制粘贴&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;状态追踪：Unstarted → Planned → Submitted → Live ，全流程标记&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;筛选：按 DR 、费用、dofollow/nofollow 、接受类型过滤&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;多产品管理：每个产品独立追踪，下拉切换&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;界面大概长这样（见截图），核心思路就是把散乱的外链提交变成一个可管理的 pipeline 。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://img.way2solo.com/photo/coryso/d2fdc516-7ea6-4c12-bbcf-f35211d3c996.png?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://shipstry.com/backlinks" rel="nofollow" target="_blank" title=""&gt;shipstry.com/backlinks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;有在做外链建设的朋友欢迎试试，也欢迎提建议 🙏&lt;/p&gt;</description>
      <author>coryso</author>
      <pubDate>Fri, 15 May 2026 18:08:34 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7339</link>
      <guid>http://www.w2solo.com/topics/7339</guid>
    </item>
    <item>
      <title>只要一张对标产品图，就能设计出新的产品，无提示词。</title>
      <description>&lt;p&gt;这个网站     &lt;a href="https://www.takingai.cn/" rel="nofollow" target="_blank"&gt;https://www.takingai.cn/&lt;/a&gt;    解决了想做新产品，但又不懂设计的难题！&lt;/p&gt;

&lt;p&gt;这个网站的初衷其实很简单，就是为了帮那些有产品设计思维，但不会使用专业绘图软件的老板，产品经理，个人。解决产品开发的难题。&lt;img src="https://img.way2solo.com/photo/yea032/c510d792-8ac3-4bda-90af-5f9aa28d9e4f.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/yea032/a33580a9-26b2-4a93-b9a5-7dc0264a560e.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;每一个做产品卖产品的公司，包括一些小店，个人，都会有开发新品的想法，尤其是一些加工厂，电商公司；都会希望在每一个阶段上新品。有时候老板自己本人或者产品经理，可能也有自己的产品开发想法，但是自己又不懂怎么使用绘图软件，然而，身边又没有设计师；若专门去外面找一个设计师，成本高，还不一定能找到合适的。这个网站就是解决这个问题的。&lt;/p&gt;

&lt;p&gt;人人都可以在上面开发自己的产品，它并不是换材质，迁移，换颜色那么简单，而是可以帮你创作出一个新的产品；虽然创作出来的图不一定能直接生产使用，但是能给你发散思维，让你的想法直观化，看得见，为你开发产品节约时间和脑力！&lt;/p&gt;

&lt;p&gt;而且网站当前还可以免费入驻设计师和供应商！当你生成的图需要专业人士调整时，就找网站内部的设计师调整；当你想打样，生成的时候，就找网站内部的供应商。也就是说，从灵感开发到落地，都包含在内了。&lt;img src="https://img.way2solo.com/photo/yea032/988e5a42-1d3c-4b89-babb-51b84c066c0c.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;本站用法很简单，只需要上传一张你想开发的对标产品图，如果你有自己想法，就把想法写在图片中；如果你想结合其他图的特征设计，就上传其他参考图；网站会自动思考，结合你所上传的所有参考图，用它自己的思维帮你创作出一个原创新品。全程不用设置，不用写提示词，会用鼠标就可以。&lt;img src="https://img.way2solo.com/photo/yea032/8647c1e2-c23b-4ddb-bf59-fb4e17aa0672.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/yea032/cd51c98c-2cff-4c6e-a30c-ccf859286113.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/yea032/fd4f9a6e-7de0-42e0-b664-d438189d64a0.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;
&lt;img src="https://img.way2solo.com/photo/yea032/88639c5e-f75f-445e-87b6-307aea19fa34.jpg?imageView2/2/w/1920/q/100" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;你感觉这个网站怎样？能帮到你吗？&lt;/p&gt;</description>
      <author>yea032</author>
      <pubDate>Fri, 15 May 2026 15:53:25 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7338</link>
      <guid>http://www.w2solo.com/topics/7338</guid>
    </item>
    <item>
      <title>最近 AI 图像生成变化太快，GPT Image 2 值得看看</title>
      <description>&lt;p&gt;最近明显感觉 AI 图像生成这个领域又开始加速了。前段时间大家还在对比 Midjourney、Nano Banana Pro、Firefly 这些工具，主要看画面质感、风格稳定性和创意表现。现在新的重点开始变成：谁能更好理解提示词、谁能更准确生成文字、谁更适合真实商业场景。&lt;/p&gt;

&lt;p&gt;我这几天也试了一些工具，感觉 GPT Image 2 这个方向挺有意思。相比单纯追求 “好看” 的图片，它更适合做一些实用型内容，比如海报、广告图、社媒配图、产品展示图、信息图这些。尤其是图片里的文字渲染，比以前很多 AI 图片工具稳定不少。&lt;/p&gt;

&lt;p&gt;现在 AI 图像工具已经不只是 “生成一张漂亮图片” 了，而是越来越接近真正的设计辅助工具。比如一个产品图、活动海报、Banner 草图，以前可能需要反复找素材、调版式，现在用提示词就能先快速生成几个方向，再慢慢优化。&lt;/p&gt;

&lt;p&gt;如果你最近也在关注 AI 图片生成，或者想找一个偏实用、偏商业创作的工具，可以试试这个：&lt;a href="https://gptimage2app.com/" rel="nofollow" target="_blank"&gt;https://gptimage2app.com/&lt;/a&gt;&lt;/p&gt;</description>
      <author>13025154939</author>
      <pubDate>Fri, 15 May 2026 13:14:51 +0800</pubDate>
      <link>http://www.w2solo.com/topics/7337</link>
      <guid>http://www.w2solo.com/topics/7337</guid>
    </item>
  </channel>
</rss>
