<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel>
<title>王星星的魔灯</title><description>记录技术成长与生活感悟</description><link>https://wxxlamp.cn/</link><language>zh-CN</language><lastBuildDate>Sun, 06 Sep 2026 06:03:48 GMT</lastBuildDate>
<atom:link href="https://wxxlamp.cn/rss.xml" rel="self" type="application/rss+xml"/>
<item><title>Agent、Skill 与模型：AI 夜校学习与工程实践思考</title><link>https://wxxlamp.cn/2026/08/18/ali-ai-night-school-agent-skill-model/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/08/18/ali-ai-night-school-agent-skill-model/</guid><pubDate>Tue, 18 Aug 2026 15:29:47 GMT</pubDate><description>结合淘天技术 AI 夜校与 Agent 开发实践，讨论模型、Agent、Skill 的边界，以及业务选型和评测方法。</description><content:encoded>&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/60fce5c0_cover.png&quot; alt=&quot;夜间 AI 课堂后的工程思考&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;1-前言&quot;&gt;1. 前言&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/1bbb01e7_section-1-v2.png&quot; alt=&quot;从 AI 夜校到七个 Agent 工程问题&quot;&gt;&lt;/p&gt;
&lt;p&gt;最近参加了淘天业务技术组织的 AI 夜校培训。因为我这段时间正好在做 Agent 开发相关的业务，所以报名参加了为期两个月的课程。上周四的第一场 Overview 讲得很好，我也想结合最近做 Agent 应用的经历，聊聊自己对 Agent、Skill 和模型的一些思考。&lt;/p&gt;
&lt;p&gt;本文已经做过脱密处理，不涉及商业机密，个人思考的成分很高。部分章节默认读者了解 LLM 和 Agent 的基础概念，没有相关经验的话，读起来可能会稍微费劲一些。&lt;/p&gt;
&lt;h1 id=&quot;2-Agent-可以代替一切吗？&quot;&gt;2. Agent 可以代替一切吗？&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/d544d05d_section-2-v2.png&quot; alt=&quot;概率模型与确定性程序在生产系统中互补&quot;&gt;&lt;/p&gt;
&lt;p&gt;从 2024 年 Agent 兴起，到 2025 年集中爆发，再到 2026 年随着 xxClaw 出现而引发“全民养虾”，LLM 加持下的 AI 仿佛什么都可以做。甚至有不少 AI 博主放出豪言：“未来没有软件，只有 AI。”&lt;/p&gt;
&lt;p&gt;现实却没有这么整齐。很多人没有接触过 WorkBuddy、Manus、QwenWork 这类办公 Agent，对 AI 的认知仍然停留在和豆包对话的程度。即使办公 Agent 的月活真的从百万增长到亿级，AI 就能替代所有 SaaS 软件吗？&lt;/p&gt;
&lt;p&gt;从软件工程的角度看，我觉得这个结论过于乐观。&lt;/p&gt;
&lt;p&gt;LLM 可以粗略理解为一个规模极大的“成语接龙”系统。即使是顶级算法研究员，目前也很难对每一次输出做出清晰归因。调用 LLM 时，即使把 &lt;code&gt;temperature&lt;/code&gt; 设为 0，多次返回也不一定完全相同。难以归因和输出不确定，是模型绕不开的特点。只靠模型能力，很难直接承担高敏感、高风险的业务操作。&lt;/p&gt;
&lt;p&gt;LLM 真正带来变化的地方，是处理非结构化数据的能力。经过大量数据和参数训练后，它“看起来”几乎可以理解各种自然语言表达，并给出合理反馈。这恰好是传统结构化程序不擅长的部分。过去需要用户在多个界面里反复操作的流程，可以通过 LLM 改造成更自然的对话式交互，但这不等于原有软件会被完全替代。&lt;/p&gt;
&lt;p&gt;在生产应用里，AI 和程序更像两个互补的角色。模型负责理解模糊意图、处理非结构化信息，程序负责确定性和结构化操作。一个最简单的例子是：生产环境通常会给 LLM 配一个计算器工具，让工具直接完成计算。模型当然也能算，但计算器的结果更稳定、更便宜，也更容易验证。&lt;/p&gt;
&lt;p&gt;成本也是一个很现实的问题。Context 和 Token 都不便宜。一件确定性的事情交给普通程序处理，CPU 和内存消耗可能很低；如果每一步都交给 LLM，资源成本和响应时间都会明显增加。&lt;/p&gt;
&lt;p&gt;当然，也可以更激进地想：如果确定性程序本身也由 AI 编写，并且写完就直接交给 AI 调用，是否意味着 AI 最终可以接管整个数字世界？某种意义上可以这样理解，但这个过程会很漫长。复杂软件架构和持续出现的增量需求，目前仍然很难完全自动化。&lt;/p&gt;
&lt;h1 id=&quot;3-Agent-的本质是什么？&quot;&gt;3. Agent 的本质是什么？&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/8cffabe2_section-3-v2.png&quot; alt=&quot;Model、Agent 与 Harness 的三层职责&quot;&gt;&lt;/p&gt;
&lt;p&gt;之前在即刻社区冲浪时，我看到不少昵称里带“xx_AI”的博主测评 DeepSeek、MiniMax、GLM 等新模型。有些人判断基模能力强不强，标准竟然是“开了多少个 Subagent”或者“能不能生成好看的视频”。后者至少还能对应模型的多模态能力，前者就很奇怪：开启多个 Subagent 本来是工程实现，和基模能力并不是一回事。&lt;/p&gt;
&lt;p&gt;那么，什么是 Agent？Agent 和 Model 的区别又在哪里？&lt;/p&gt;
&lt;p&gt;关于模型，可以参考我去年写的 &lt;a href=&quot;https://wxxlamp.cn/2025/12/24/ai-ml-getting-started/&quot;&gt;LLM 入门文章&lt;/a&gt;。简单讲，模型的核心仍然像“成语接龙”：基于海量参数，通过向量和矩阵运算，把 Prompt Token 作为输入，再逐步生成输出。多模态模型的机制也类似，只是输入和输出变成了经过特殊编码的图片、音频或视频 Token。&lt;/p&gt;
&lt;p&gt;看过 Codex、OpenCode 等开源 Agent 工具源码的人应该会发现，所谓 Agent，本质上是对模型 API 调用的一层工程封装。它把用户输入、工具和 Skill 的描述、系统信息等内容组装成上下文，传给模型 API；模型再逐字返回 Thinking Delta、Text Delta，以及要执行的工具名称和输入参数。&lt;/p&gt;
&lt;p&gt;Agent 的价值，在于统一管理 LLM API 调用的运行时，并对外提供 ReAct、Handoff 等调用模式。开发者主要关注 Agent 的 Prompt、Tool Config 和 Skill，不需要反复适配下游模型接口的协议和参数。&lt;/p&gt;
&lt;p&gt;再往外扩一层，现在的大型 Agent 平台的边界已经超过单个 Agent，更接近 Harness 套件。除了模型调用和上下文组装，它们还会处理记忆抽取与存储、状态恢复、工具执行，甚至自迭代优化。这些都属于 Harness 工程的一部分。&lt;/p&gt;
&lt;p&gt;不过，开发者也没必要在所有场景里都服从 Agent 这层抽象。有时我们只想定制模型接口的某个参数，如果还要专门新建一套 Agent Prompt、Tool 和 Skill，多少有点“大炮打蚊子”。抽象应该解决复杂度，不能反过来制造复杂度。&lt;/p&gt;
&lt;p&gt;很多基模会强调自己的 Agentic 能力。一个重要原因是：模型在训练阶段接触了大量 Function Call、Thinking Delta、JSON Schema、System Prompt 等格式的数据，所以它进入 Agent 环境后，更容易生成符合约定的工具调用信息。这类模型通常会被称为 Agentic Model。但工具编排方式、Subagent 数量和运行时恢复能力，仍然属于模型之外的工程系统。&lt;/p&gt;
&lt;h1 id=&quot;4-关于-Skill-的思考&quot;&gt;4. 关于 Skill 的思考&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/e9159408_section-4.png&quot; alt=&quot;Program、Skill 与 Subagent 的工程选型&quot;&gt;&lt;/p&gt;
&lt;p&gt;今年年初，Anthropic 提出并带火 Skill 的概念后，很多朋友，尤其是不做开发的朋友，会觉得什么事情都可以写一个 Skill 解决。&lt;/p&gt;
&lt;p&gt;我和产品同学交流时，经常会听到一句话：“快把这个东西抽成一个 Skill，以后我就不找你了。”这句话有对的部分，也有明显的边界。Skill 确实可以沉淀经验，但很多问题并不会因为写了一份 Skill 就自动解决。&lt;/p&gt;
&lt;p&gt;如果了解 Agent 开发，就会知道 Skill 本质上是一套工作 SOP。为了避免模型上下文被撑爆，Skill 的内容通常会渐进式加载。因此，Skill 可以看成一个更大、更结构化的 Prompt。它和我们在豆包输入框里打出的指令，在本质上没有区别；差别主要在于它不会一开始就被全文塞进上下文，而是根据任务逐步加载，以节省上下文并减少对推理效果的干扰。&lt;/p&gt;
&lt;p&gt;既然 Skill 描述的是一套 SOP，万物都可以 Skill 化吗？理论上可以，实际没有必要。&lt;/p&gt;
&lt;p&gt;我们不应该为了 AI，把已有的 SOP 和 Workflow 全部重新包装成 Skill。一方面，程序化 Workflow 的执行成本很低，稳定性也更高；另一方面，同一份 Skill 放在不同 Agent 中，会受到上下文风格、基模能力和恢复机制的影响，结果很难像程序流程一样精确。Skill 的文本是确定的，执行结果却仍然带有模型的不确定性。目前市场上也缺少足够成熟、统一的 Skill 评估和优化工具，想要把 Skill 精准地规模化使用，还有很长的路要走。&lt;/p&gt;
&lt;p&gt;另一个容易忽略的问题是，Skill 全文可以被 Agent 读取。开放一个 Skill，某种程度上等于把其中沉淀的经验全部暴露出来。对于包含敏感经验或内部知识的场景，这个风险不能忽略。&lt;/p&gt;
&lt;p&gt;所以在多数确定性任务中，用 Skill 代替程序，往往只是“为了 AI 而 AI”。例如，把图片上传到 GitHub，可以直接写脚本调用 GitHub 接口，也可以写 Skill 让模型通过 Browser Use 操作页面。显然，脚本更快、更稳定，也更省心。&lt;/p&gt;
&lt;p&gt;Skill 更适合难以完全程序化、但又能总结出操作经验的业务流程。它可以结合 MCP 和工具完成整体编排。譬如交接场景，即将离职的同学可以把操作手册整理成 Skill，接手的同学再借助 AI 完成过去需要反复询问的任务。这里的价值在于让隐性的经验变得可执行，确定性环节仍然交给程序。&lt;/p&gt;
&lt;p&gt;还有一个更偏技术的问题：Subagent 和 Skill 到底有什么区别？对直接使用 WorkBuddy 等 Agent 工具的非技术同学来说，这个问题可能有些奇怪——Subagent 是一个 Agent，Skill 是 Agent 的一套 SOP，两者为什么要放在一起比较？&lt;/p&gt;
&lt;p&gt;从工程实现看，Agent 和 Skill 最后都会变成一组传给模型接口的上下文。因此在实际开发中，经常会遇到同一个选择：一个任务究竟应该作为 Skill 放进主 Agent，还是单独交给一个 Subagent？&lt;/p&gt;
&lt;p&gt;我的判断是，Subagent 更适合任务可以独立完成、需要独立上下文，或者能够并行执行的场景；主 Agent 加 Skill 更适合需要持续共享上下文的通用流程。使用 Subagent 时，还要特别注意 Handoff 过程里的中间产物如何转交，否则上下文隔离带来的好处，很可能会变成信息丢失。&lt;/p&gt;
&lt;h1 id=&quot;5-Agent-的常见业务场景&quot;&gt;5. Agent 的常见业务场景&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/a37b6bc8_section-5-v2.png&quot; alt=&quot;Agent 在开发、服务、学习和需求理解中的场景&quot;&gt;&lt;/p&gt;
&lt;p&gt;讨论到这里可以看到，Agent 很难替代软件完成所有工作；有了 Agent，也不意味着业务营收和利润会自然增长。&lt;/p&gt;
&lt;p&gt;Agent 首先是一种提高效率的工具。它可以帮助程序员完成从需求理解到代码实现的端到端开发，也可以帮助非技术同学排查工单、处理 Excel、写周报。更重要的是，其中很多事情能够并行执行。标准化程度较高、但处理过程复杂的工作可以交给 Agent，人则把精力留给需要判断和创造的部分。&lt;/p&gt;
&lt;p&gt;因为能处理大量非结构化数据，Agent 也天然适合客服、销售和 QA，新人 Landing、工作交接同样是常见场景。我之前在 WAIC 上看到，金融系统几乎都在尝试基于 Agent 做答疑客服。听起来这些实践还比较早期，但方向很直观：先把分散的知识和操作流程串起来，再逐步提高任务完成度。&lt;/p&gt;
&lt;p&gt;学习也是一个很适合 Agent 的场景。Agent 不会因为重复提问失去耐心，可以按学习者的理解程度调整讲解方式，知识覆盖面也足够广。不过，知识准确性和教学节奏仍然需要评测，尤其是在专业领域里，不能因为回答流畅就默认它是对的。&lt;/p&gt;
&lt;p&gt;再往算法方向看，还可以用 LLM 推理做更细致的用户需求理解，补充传统结构化特征难以表达的信息，从而形成更精细的用户画像，再作用于搜索和推荐场景。&lt;/p&gt;
&lt;p&gt;目前看来，大部分 Agent 场景更确定的价值仍然是效率提升。希望靠 AI 直接缓解全部业务压力的团队，可能需要先重新审视业务模式，看看问题究竟来自重复劳动、信息处理，还是产品本身。把所有期待都压在 AI 上，很容易错过真正应该解决的问题。&lt;/p&gt;
&lt;h1 id=&quot;6-Agent-开发的选型和注意点&quot;&gt;6. Agent 开发的选型和注意点&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/b16dbcdd_section-6.png&quot; alt=&quot;Agent 开发中的运行方式、流程与交互取舍&quot;&gt;&lt;/p&gt;
&lt;p&gt;对技术同学来说，很多特定业务无法直接通过 WorkBuddy、QwenWork 等通用桌面 Agent 完成，往往还是要调用模型厂商的接口，自行管理上下文。在这个场景下，有几件事需要优先想清楚。&lt;/p&gt;
&lt;p&gt;首先是 Agent 运行在用户本地还是云端。本地运行时，消息断连和服务恢复相对简单；如果运行在云端，就要处理消息断联、服务器重启、系统宕机、上下文存储和场景记忆等异常情况。云端也有明显优势：可以统一收敛用户数据，并且 7×24 小时执行业务。很多厂商已经意识到这一点，阿里之前的 MuleRun、Qoder Agent Cloud 等产品，都是把 Agent 封装到云端，再让用户通过 API 调用。&lt;/p&gt;
&lt;p&gt;其次是 ReAct 和 Workflow 的选择。现在的通用 Agent 几乎都有 ReAct 的影子，也出现了 Codex（OpenAI 也开源了同名框架）、DeepSeek Harness、AgentScope 等实现。ReAct 通过“思考—行动—观察”让模型自主调用工具，适合解决开放问题；但很多时候只有 ReAct 还不够，开发者仍然要提供更确定的 Workflow。&lt;/p&gt;
&lt;p&gt;Workflow 的优势是确定性高、成本更可控、执行速度更快。一种常见做法是让 ReAct 先生成 Dynamic Workflow，由 AI 决定流程，再交给程序完成执行。常见的 Plan 模式也可以看成这种形态的变体。实际选型时，需要把“由模型判断什么”和“由程序保证什么”明确拆开。&lt;/p&gt;
&lt;p&gt;真正写 Agent 时，上下文管理、记忆和缓存也很重要。用户消息和系统消息如何定位与插入？什么时候抽取用户记忆？记忆存在哪里、如何召回？怎样组装上下文才能更好地利用 Model Cache？工具输入输出如何压缩？每次会话又应该如何管理？这些问题很难靠一个统一框架全部解决，往往要结合产品形态逐个设计。这里先把问题列出来，具体方案以后有机会再展开。&lt;/p&gt;
&lt;p&gt;最后，开发 Agent 应用不要拘泥于 Chat。用了聊天框不等于产品就更智能。Agent 真正擅长的是处理非结构化信息和理解模糊意图，交互方式仍然要从产品视角出发：明明一个按钮就能解决的事情，为什么一定要让用户输入一段话？&lt;/p&gt;
&lt;h1 id=&quot;7-如何做-Agent-评测&quot;&gt;7. 如何做 Agent 评测&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/7e849ca7_section-7.png&quot; alt=&quot;Agent 运行指标、业务标准、问题诊断和历史回归闭环&quot;&gt;&lt;/p&gt;
&lt;p&gt;在传统软件工程中，开发和测试是两个重要环节；Agent 开发同样离不开评测。验证 Agent 能否完成基本功能只是第一步。更困难的问题是：它在非结构化场景里究竟好不好用、能不能稳定使用、是否值得使用，Bad Case 如何发现，出现问题后又怎样快速定位和优化。&lt;/p&gt;
&lt;p&gt;我觉得 Agent 评测至少要解决三个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基础功能：保证 Agent 的产品功能满足业务诉求。&lt;/li&gt;
&lt;li&gt;稳定性：覆盖不同输入、不同时间和各种 Bad Case，检查输出是否持续符合预期。LLM 的输出带有概率性，评测不能只依赖一次人工体验，因此 Agent 评测需要尽可能自动化。&lt;/li&gt;
&lt;li&gt;优化建议：评测结果除了说明 Agent 是否符合预期，还要结合执行链路定位可以优化的位置，例如 Prompt、Cache 命中率、Skill 或工具描述。最好还能评估单次执行成本，用来判断真实 ROI 是否满足业务预期。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;要做到这些，评测体系还需要几类基础能力：&lt;/p&gt;
&lt;p&gt;第一，采集 &lt;code&gt;cache hit rate&lt;/code&gt;、&lt;code&gt;TTFT&lt;/code&gt;、&lt;code&gt;token cost&lt;/code&gt;、&lt;code&gt;time cost&lt;/code&gt; 等运行指标。没有这些数据，很难判断一次优化究竟改善了效果，还是只增加了成本。&lt;/p&gt;
&lt;p&gt;第二，围绕业务预期设计评测标准。Agent 输出很难只用一个通用分数衡量，评测项必须能回答它是否完成了具体业务目标。&lt;/p&gt;
&lt;p&gt;第三，识别和定位 Bad Case。发现结果不符合预期之后，还要继续判断问题出在 Prompt、Skill、工具、上下文组装，还是模型本身。&lt;/p&gt;
&lt;p&gt;第四，沉淀历史评测结果，让每一次改动都能回溯，并和之前的版本关联。否则 Prompt 或 Skill 调整后，很容易只修好眼前的 Case，却把旧问题重新带回来。&lt;/p&gt;
&lt;p&gt;这部分我目前更多是在总结问题和方向，具体的评测实现还没有在本文里展开。等后续实践更完整，再单独写一篇。&lt;/p&gt;
</content:encoded></item>
<item><title>基于 Codex 插件的语雀多平台内容发布实践</title><link>https://wxxlamp.cn/2026/08/02/yuque-multichannel-publisher/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/08/02/yuque-multichannel-publisher/</guid><pubDate>Sun, 02 Aug 2026 09:28:50 GMT</pubDate><description>我把语雀长文的润色、配图、三端改写、质量检查和草稿投递收进一个 Codex Plugin，也记录平台自动化的真实边界。</description><content:encoded>&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/b2c5bb2c_cover.png&quot; alt=&quot;一篇长文经过可恢复工作流生成三端发布包&quot;&gt;&lt;/p&gt;
&lt;p&gt;我一般先在语雀里把文章写完。&lt;/p&gt;
&lt;p&gt;文字落下最后一句后，事情却还没结束：博客、微信公众号和小红书各有一套内容形态，我还要重新排版、配图、拆文和检查，一轮下来大概要再花两个小时。&lt;/p&gt;
&lt;p&gt;最近我把这段流程做成了一个 &lt;a href=&quot;https://github.com/wxxlamp/ai-coding-config/tree/main/plugins/yuque-multichannel-publisher&quot;&gt;Codex Plugin&lt;/a&gt;。它会读取语雀原文，让 AI 完成润色、配图和平台改写，再由脚本负责上传、校验、落盘、断点恢复和发布前检查。这篇文章也正好是它的第一份完整测试。我想把制作过程记下来，也讲清楚哪些步骤真的能自动化，哪些仍然需要人在平台页面确认。&lt;/p&gt;
&lt;h1 id=&quot;1-为什么要把内容分发做成插件&quot;&gt;1. 为什么要把内容分发做成插件&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/69c25415_section-1.png&quot; alt=&quot;从一篇语雀长文到博客、公众号和小红书的重复处理流程&quot;&gt;&lt;/p&gt;
&lt;p&gt;我一直有写博客的习惯，日常记录通常先放在语雀。文章完成后，我会先发布到个人网站，再处理微信公众号和小红书。三个后台看着都能粘贴 Markdown，真正做起来却各有一摊事情。&lt;/p&gt;
&lt;p&gt;博客需要完整的结构、代码块和 Hexo front matter；公众号更关心手机阅读体验，还要有单独的 2.35:1 封面；小红书需要把长文重新组织成一篇短笔记和一组竖版卡片。图片也不能混着用，正文首图、章节图、公众号封面和小红书卡片承担的任务都不一样。&lt;/p&gt;
&lt;p&gt;最开始，我在仓库的 &lt;code&gt;.agents/skills/&lt;/code&gt; 下面陆续写了几个 Skill：拉取语雀、迁移图片、上传图床、润色 Markdown。单独使用都没问题，组合起来却越来越难管。&lt;/p&gt;
&lt;p&gt;比如，语雀 Skill 只知道把文章下载到本地，不知道最终要放到哪里；润色 Skill 可以学习语气，却不知道公众号应该保留多少博客内容；图片上传 Skill 更不会判断一张图是否真的解释了章节。每次执行时，AI 都要重新拼接这些能力，目录、参数和上下文也容易漏掉。把仓库复制给别人以后，还可能缺少某个 &lt;code&gt;.agents/skills&lt;/code&gt; 依赖。&lt;/p&gt;
&lt;p&gt;另外，这条链路本身很长。语雀读取、润色、生图、上传、三端改写和发布，中间任何一步遇到网络或页面问题，任务都可能中断。如果进度只留在当前会话里，下次只能重新梳理。&lt;/p&gt;
&lt;p&gt;这些问题让我确定了一件事：需要一个完整的 Plugin 来约束整条工作流。它要带着自己的说明、脚本、reference、测试和产物契约，也要把个人凭据与任务进度留在工作区。这样复制插件时不会带走 token，任务恢复时也不用猜上次做到了哪里。&lt;/p&gt;
&lt;h1 id=&quot;2-我是如何做出这个插件的&quot;&gt;2. 我是如何做出这个插件的&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/f2fe3dfa_section-2.png&quot; alt=&quot;插件从读取、AI 加工、脚本处理到质量检查和三端输出的五步流程&quot;&gt;&lt;/p&gt;
&lt;p&gt;插件和这篇文章全程使用 Codex 完成。下面讲一下大概的思路。&lt;/p&gt;
&lt;h2 id=&quot;2-1-先划清-AI-与脚本的边界&quot;&gt;2.1. 先划清 AI 与脚本的边界&lt;/h2&gt;&lt;p&gt;语气判断、内容补充、图片创意和平台改写都需要结合上下文，我把它们交给 AI。AI 还要做最后一次复审，检查事实边界、标题编号、图片语义和平台差异。&lt;/p&gt;
&lt;p&gt;下载文件、上传图床、检查图片比例、比较公众号与博客正文、创建目录和记录状态都有明确结果，适合用脚本完成。脚本只判断“文件在不在”“比例对不对”“字段齐不齐”，不会偷偷替作者改正文。&lt;/p&gt;
&lt;p&gt;这条边界后来成了插件的核心。AI 可以发挥，但必须留下可检查的产物；脚本保持稳定，也不会把创作任务简化成字符串替换。&lt;/p&gt;
&lt;h2 id=&quot;2-2-把零散-Skill-收进一个-Plugin&quot;&gt;2.2. 把零散 Skill 收进一个 Plugin&lt;/h2&gt;&lt;p&gt;原来的语雀拉取、图床上传和语气缓存被改造成插件内部组件。对外只保留一个主 Skill，由它描述完整工作流和质量要求。AI 不再临时寻找多个 Skill，也不依赖目标仓库的 &lt;code&gt;.agents/&lt;/code&gt; 目录。&lt;/p&gt;
&lt;p&gt;语雀读取优先使用已经登录的浏览器会话。这样可以处理私有文档、非会员页面和接口策略变化；页面能复制 Markdown 时直接保留格式，复制能力不可用时再根据可见结构还原标题、列表、引用、代码和图片。原图会重新上传到用户自己的图床。&lt;/p&gt;
&lt;h2 id=&quot;2-3-用-86-篇旧文生成作者语气基线&quot;&gt;2.3. 用 86 篇旧文生成作者语气基线&lt;/h2&gt;&lt;p&gt;我不希望每次润色都重新读取历史文章，所以先让 AI 阅读了博客里原有的 86 篇文章，整理出一份 &lt;code&gt;author-voice.md&lt;/code&gt;。这份个人档案保存在工作区 &lt;code&gt;.codex/yuque-multichannel-publisher/style-profiles/&lt;/code&gt;，不会打包进 Plugin。&lt;/p&gt;
&lt;p&gt;别人安装插件后，第一次运行也会走同样的学习过程，只读取他自己工作区里的历史文章。如果工作区暂时没有可用样本，插件会明确使用克制的通用语气，等积累了文章再建立个人档案。这个过程只生成语气 reference，不会生成任何密钥。图床 token 需要用户通过环境变量或工作区配置提供，公众号和小红书则使用用户已经登录的平台会话。&lt;/p&gt;
&lt;p&gt;这份 reference 记录了第一人称工程师视角、常见开头、句段节奏、技术文章的推理方式、结尾习惯，以及需要避免的表达。后续润色只读取这份紧凑基线。旧文发生变化时再更新语料指纹；某个分类确实有明显差异时，再补一个小型分类档案。&lt;/p&gt;
&lt;p&gt;语气保障分成两遍。第一遍先守住事实，只补理解文章所需的背景、因果、例子和边界；第二遍才按照语气档案重组句段，保留我的第一人称判断、技术推理、犹豫和自然转场。这样可以避免为了“像我”而编出新的经历。&lt;/p&gt;
&lt;p&gt;完成后再做去 AI 痕迹复审，重点处理模板化开场、空泛黑话、整齐排比、万能总结和高频的否定对照句。项目会记录本次使用的语气档案路径、语料指纹、检查项和复审说明。脚本负责确认这些证据存在并扫描高风险表达，最终语言判断仍由 AI 完成。&lt;/p&gt;
&lt;h2 id=&quot;2-4-让图片先有内容，再谈风格&quot;&gt;2.4. 让图片先有内容，再谈风格&lt;/h2&gt;&lt;p&gt;第一版章节图虽然比例正确，看起来也挺“科技”，但画面和章节没有多少关系。这个问题促使我把图片生成拆成三个动作：先写视觉 brief，再生图，最后看图复审。&lt;/p&gt;
&lt;p&gt;brief 会写清本节的核心判断、必须出现的对象与关系、适合的信息类型，以及需要避开的元素。流程和架构优先使用编辑式信息图；有真实材料时优先用截图；个人经历更适合叙事插画。生成后还要检查文字、关系和缩略图可读性，内容对不上就重画。&lt;/p&gt;
&lt;p&gt;正文首图保留温暖手绘动画氛围。公众号封面单独生成，严格使用 2.35:1，并在安全区加入短标题。小红书先组织卡片叙事，每张卡只推进一个信息点，然后再制作 3:4 图片。&lt;/p&gt;
&lt;h2 id=&quot;2-5-用检查点保存长任务&quot;&gt;2.5. 用检查点保存长任务&lt;/h2&gt;&lt;p&gt;每个内容项目都会在 &lt;code&gt;content-projects/&amp;lt;slug&amp;gt;/.codex/&lt;/code&gt; 下保存 &lt;code&gt;state.json&lt;/code&gt; 和事件记录。当前阶段、已完成产物、哈希和最终目标目录都在里面。&lt;/p&gt;
&lt;p&gt;插件把任务分成初始化、拉取、润色、配图、复审和持久化几个阶段。中断后运行 &lt;code&gt;resume&lt;/code&gt;，可以直接看到上次停在哪里。只有记录了 &lt;code&gt;reviewed&lt;/code&gt; 检查点，&lt;code&gt;materialize&lt;/code&gt; 才允许把草稿写入正式目录。&lt;/p&gt;
&lt;p&gt;最后，我给流水线补了契约测试。现在会检查标题编号、图片比例和 brief、作者语气指纹、公众号正文相似度、小红书卡片数量、跨轮指代、最终目录，以及各平台是否被误记成同一个发布状态。这次测试文章跑完后，草稿校验、物化校验和 Hexo 构建都通过了。&lt;/p&gt;
&lt;h1 id=&quot;3-插件的结构、产物和优点&quot;&gt;3. 插件的结构、产物和优点&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/2000d4ab_section-3.png&quot; alt=&quot;一份经审稿的长文分别落盘为博客、公众号和小红书发布包&quot;&gt;&lt;/p&gt;
&lt;p&gt;插件现在只有一个对外 Skill，目录大致如下：&lt;/p&gt;
&lt;figure class=&quot;highlight text&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;yuque-multichannel-publisher/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── .codex-plugin/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   └── plugin.json&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── README.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└── skills/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    └── yuque-multichannel-publisher/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        ├── SKILL.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        ├── assets/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   └── config.example.json&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        ├── references/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── voice-profile.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── editorial-guide.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── visual-direction.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── artifact-contract.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── style-profile-cache.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   └── publishing.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        ├── scripts/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── pipeline.py&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── workspace.py&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── yuque_fetcher.py&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   ├── image_uploader.py&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        │   └── style_profiles.py&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        └── tests/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            └── test_pipeline.py&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code&gt;plugin.json&lt;/code&gt; 负责插件身份、版本和 Codex 中的展示信息；&lt;code&gt;SKILL.md&lt;/code&gt; 告诉 AI 应该怎样完成整条工作流；&lt;code&gt;references/&lt;/code&gt; 存放通用的语气学习、编辑、配图、产物和发布规范；&lt;code&gt;scripts/&lt;/code&gt; 处理确定性操作；&lt;code&gt;tests/&lt;/code&gt; 防止目录和校验规则在更新后悄悄失效。&lt;/p&gt;
&lt;p&gt;个人数据单独放在工作区：&lt;/p&gt;
&lt;figure class=&quot;highlight text&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;workspace&amp;gt;/.codex/yuque-multichannel-publisher/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── config.json&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── browser-data/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;└── style-profiles/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ├── author-voice.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ├── profile-index.json&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    └── style-*.json&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;真正运行时，个人语气、登录态、图床 token、分类缓存和任务状态不会写进插件目录。它们保存在工作区的 &lt;code&gt;.codex/yuque-multichannel-publisher/&lt;/code&gt; 或具体内容项目下。插件只检查凭据是否已配置，不会生成密钥，也不会读取浏览器密码。插件可以更新或复制，个人配置不会被覆盖。&lt;/p&gt;
&lt;h2 id=&quot;3-1-三个平台如何落盘&quot;&gt;3.1. 三个平台如何落盘&lt;/h2&gt;&lt;p&gt;博客保留完整论证、代码、引用和目录结构，最终写入：&lt;/p&gt;
&lt;figure class=&quot;highlight text&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;source/_posts/&amp;lt;slug&amp;gt;.md&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;公众号与博客使用同一份正文，只做轻量段落适配，同时生成 Markdown 和带行内样式的 HTML：&lt;/p&gt;
&lt;figure class=&quot;highlight text&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;wechat/&amp;lt;slug&amp;gt;/article.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;wechat/&amp;lt;slug&amp;gt;/article.html&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;小红书先判断原文能支撑几篇独立笔记。每一轮都要重新交代对象和核心观点，并生成正文与卡片计划：&lt;/p&gt;
&lt;figure class=&quot;highlight text&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;rednote/&amp;lt;slug&amp;gt;/series-plan.json&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;rednote/&amp;lt;slug&amp;gt;/round1/post.md&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;rednote/&amp;lt;slug&amp;gt;/round1/cards.json&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这篇文章最终只生成一轮。动机、制作流程和插件结构属于同一个主题，拆成三轮后，后两篇会缺少必要上下文。&lt;/p&gt;
&lt;h2 id=&quot;3-2-小红书如何决定拆成几轮&quot;&gt;3.2. 小红书如何决定拆成几轮&lt;/h2&gt;&lt;p&gt;小红书拆分先看主题，再看章节。AI 会从长文里提取文章对象、目标读者、核心判断、可执行信息和真实材料，然后逐个判断候选主题离开其他内容后是否仍然完整。&lt;/p&gt;
&lt;p&gt;一轮至少要回答三个问题：讲的是什么、为什么值得看、读者能怎么做。它还要有自己的背景、收益和配图材料。只是长文里的一个步骤，或者需要依赖“上一轮”才能理解，就继续留在同一篇笔记里。&lt;/p&gt;
&lt;p&gt;轮数确定后，每轮先写 &lt;code&gt;series-plan.json&lt;/code&gt; 里的上下文，再写 &lt;code&gt;cards.json&lt;/code&gt;。首卡说明主题和目标读者，中间每卡只推进一个信息点，末卡收住结论或提出具体问题。图片优先使用真实截图和材料，其次才是重绘、图文卡和 AI 场景图。最后单独阅读每轮正文和卡片，做一次“陌生读者测试”。&lt;/p&gt;
&lt;p&gt;这篇文章包含动机、制作过程、插件结构和优势，但它们都在回答“这个插件是怎么做出来的”。拆开后，结构和优势会失去产品背景，所以最终只保留一轮。&lt;/p&gt;
&lt;h2 id=&quot;3-3-先检查，再决定怎样投递草稿&quot;&gt;3.3. 先检查，再决定怎样投递草稿&lt;/h2&gt;&lt;p&gt;我参考了 &lt;a href=&quot;https://github.com/geekjourneyx/md2wechat-skill&quot;&gt;md2wechat&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/white0dew/XiaohongshuSkills&quot;&gt;XiaohongshuSkills&lt;/a&gt; 的做法，把“生成发布包”和“操作外部平台”拆成两步。流水线先输出机器可读的就绪报告：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;python3 pipeline.py inspect --project &amp;lt;slug&amp;gt; --probe&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;报告会列出正文字符数、标题数量、远程图片、摘要长度、AI 套话风险、三端缺失产物和适配器状态。后面的动作只认这份报告里的 blocker，不再根据一句“文件已经生成”猜测能不能发布。&lt;/p&gt;
&lt;p&gt;微信公众号可以接入用户单独安装的 &lt;code&gt;md2wechat&lt;/code&gt;。插件先调用 &lt;code&gt;inspect&lt;/code&gt; 检查账号配置、封面和目标状态，再执行草稿写入。AppID、Secret 和 API Key 都留在外部工具自己的配置里，插件只保存可执行文件路径和账号别名。&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;python3 pipeline.py send-draft \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --project &amp;lt;slug&amp;gt; \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --channel wechat \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --confirm&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;小红书这边要保守一些。参考项目通过 Chrome DevTools Protocol 操作创作后台，能上传图片、填写标题和正文，但自动化会受页面改版、登录校验和账号风控影响。插件因此固定调用它的 &lt;code&gt;--preview&lt;/code&gt; 模式：只填充编辑器，不自动点击发布，也不会把“页面填好了”写成“平台草稿已保存”。&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;python3 pipeline.py send-draft \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --project &amp;lt;slug&amp;gt; \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --channel rednote \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --round round1 \&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  --confirm&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;小红书页面明确提示保存成功后，才记录 &lt;code&gt;draft_saved&lt;/code&gt;。微信公众号、小红书和每个小红书轮次都有自己的状态；&lt;code&gt;filled_for_review&lt;/code&gt;、&lt;code&gt;draft_saved&lt;/code&gt;、&lt;code&gt;published&lt;/code&gt; 三者也完全分开。这样某一轮失败时，不会把其他平台已经完成的内容阶段一起回滚。&lt;/p&gt;
&lt;p&gt;这两个外部项目都不直接打包进插件。&lt;code&gt;md2wechat&lt;/code&gt; 的许可对商业使用和再分发有额外要求；&lt;code&gt;XiaohongshuSkills&lt;/code&gt; 的浏览器选择器也需要跟着平台更新。保留适配层以后，插件本身只维护内容契约、能力探测和状态记录，外部工具可以独立安装与升级。&lt;/p&gt;
&lt;h2 id=&quot;3-4-我觉得它目前最有价值的地方&quot;&gt;3.4. 我觉得它目前最有价值的地方&lt;/h2&gt;&lt;p&gt;第一，插件是自包含的。复制整个目录就能带走主 Skill、内部脚本、reference 和测试，不需要再拼装仓库里的多个 Skill。&lt;/p&gt;
&lt;p&gt;第二，AI 的创作能力和脚本的稳定性都有明确位置。文章、图片和渠道改写仍由 AI 完成，目录、比例、状态和相似度交给脚本检查。出现问题时，也比较容易知道应该改 prompt、reference 还是代码。&lt;/p&gt;
&lt;p&gt;第三，长任务可以恢复。图片生成或浏览器操作中断后，原文、草稿、图片和检查点都还在本地，下次会话可以从有效阶段继续。&lt;/p&gt;
&lt;p&gt;第四，三个平台共享事实和长文底稿，但产物形态各自独立。公众号不会被改成摘要，小红书也不会按一级标题机械拆轮。&lt;/p&gt;
&lt;p&gt;第五，质量要求能够留下记录。作者语气使用了哪份 reference、章节图要表达什么、公众号封面标题是否检查过、小红书为什么分成当前轮数，都可以在项目文件中找到。&lt;/p&gt;
&lt;p&gt;它目前还有边界。公众号草稿箱可以通过外部适配器自动写入，但凭据、IP 白名单和账号权限仍要由用户配置；小红书自动化目前可靠做到的是编辑器填充，草稿是否真正保存必须看平台反馈。博客仓库也有自己的 Git 规则。真正发布前，仍然要确认账号、标题、时间和可见范围。&lt;/p&gt;
&lt;p&gt;插件源码和使用说明已经放在 &lt;a href=&quot;https://github.com/wxxlamp/ai-coding-config&quot;&gt;wxxlamp/ai-coding-config&lt;/a&gt; 仓库，进入 &lt;code&gt;plugins/yuque-multichannel-publisher/&lt;/code&gt; 目录即可查看完整 Plugin。&lt;/p&gt;
&lt;p&gt;对我来说，这个插件已经把最烦的搬运工作收进了一条可重复的流程。以后文章写完，我可以先把注意力留在内容上，等本地发布包检查通过，再决定什么时候打开各个平台。&lt;/p&gt;
</content:encoded></item>
<item><title>基于 AgentScope 的 AI 运营平台设计与实践</title><link>https://wxxlamp.cn/2026/07/19/agentscope-ai-operations-platform-v1/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/07/19/agentscope-ai-operations-platform-v1/</guid><pubDate>Sun, 19 Jul 2026 15:58:00 GMT</pubDate><description>基于AgentScope总结AI运营平台的工程实践，覆盖提示词、状态机、主子Agent、工具治理、联调验证与体验优化。</description><content:encoded>&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/52d30cd1_agentscope-ops-platform-cover.png&quot; alt=&quot;AI 运营平台&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;很多人上手 Agent 开发，第一反应是把 Agent loop 跑起来——模型调工具、拿结果、再进入下一轮。但真正把一个面向运营的 AI 平台做到可用，你会发现 Loop 只是起点。这篇文章基于 AgentScope，聊聊我们在构建 AI 运营平台的过程中，除了 Loop 之外还踩过、想过的那些事：提示词与技能怎么分工、长流程怎么引导、多下游接口怎么保证参数正确。（文中隐去了内部平台地址、系统名称与具体业务数据，只保留可复用的工程实践。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;一、核心问题与解决方案&quot;&gt;一、核心问题与解决方案&lt;/h2&gt;&lt;p&gt;做这类运营智能体，绕不开下面三个核心问题，我把我们最终采用的做法也一并列在后面。&lt;/p&gt;
&lt;h3 id=&quot;核心问题&quot;&gt;核心问题&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;如何让算法的策略被模型有效识别？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拆分人群圈选、触达、权益等不同领域 agent，各域 agent 维护单独的上下文和 skill&lt;/li&gt;
&lt;li&gt;借助 AgentScope，实现 Agent loop 和基本的 skills、mcp 管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;如何正确引导运营操作长流程、不乱操作？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;workflow + 前端流程渲染&lt;/li&gt;
&lt;li&gt;ask user question 卡片&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;如何对接多个下游系统、保证参数的正确？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对接营销投放平台、触达平台、数据仓库、低代码平台、协同文档等多个工具和 MCP&lt;/li&gt;
&lt;li&gt;发券、触达等 tools，通过上下文 extract &amp;amp; parse 对应的核心参数，避免模型自己填写&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;架构与解决方案&quot;&gt;架构与解决方案&lt;/h3&gt;&lt;p&gt;下图是脱敏后的整体架构示意，你可以对照后文的每个模块来看：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/fc72e4a1_agentscope-ops-platform-architecture.png&quot; alt=&quot;AI 运营平台架构&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;二、提示词、技能与会话&quot;&gt;二、提示词、技能与会话&lt;/h2&gt;&lt;h3 id=&quot;Prompt-与-Skills-分工的思考&quot;&gt;Prompt 与 Skills 分工的思考&lt;/h3&gt;&lt;p&gt;如果你刚上手，多半会像我们一样先把主 Agent 的 prompt 写得很简单，把路由和 workflow 管理放到 skill 里。但很快你会发现：模型有时候根本不加载对应的 skill，而是直接依赖 system prompt 自由发挥，整体效果因此打折。这里有两条建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;skills 是渐进式披露和加载。如果一个数据很重要、且每轮对话都会用到，就直接放进 system prompt，不要为了省 token 塞到 skills 里，否则效果会大打折扣；&lt;/li&gt;
&lt;li&gt;虽然 system prompt 常被建议控制在 ~500 行以内，但起步阶段不必拘泥于这个限制。你可以先写一个大而全的 system prompt，等效果稳定后，再通过 reference 优化它的长度。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;动态-prompt-防止-Lost-in-the-Middle&quot;&gt;动态 prompt 防止 Lost in the Middle&lt;/h3&gt;&lt;p&gt;基于 Transformer 的 LLM，注意力容易往两端分散，中间的信息有可能被”遗忘”；再加上压缩机制，agent 还可能把工具的结果压缩掉。所以请记住一条原则：真正重要的信息，不要交给模型自己管理，而要人工干预。&lt;/p&gt;
&lt;p&gt;我们的做法是通过 AgentScope 的 middleware 能力，在每轮会话开始时，把重要信息从内存中抽象出来，用 &lt;code&gt;&amp;lt;Context-Reminder&amp;gt;&amp;lt;/Context-Reminder&amp;gt;&lt;/code&gt; 标签强制追加到 system prompt 中，以此保证模型对关键信息保持”绝对注意力”。&lt;/p&gt;
&lt;p&gt;Context-Reminder 中通常包含这些内容：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;活动基本信息（活动名称、用户ID等）、活动阶段&amp;amp;流程控制、人群信息、外部接口的id等&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;如何优化-Prompt&quot;&gt;如何优化 Prompt&lt;/h3&gt;&lt;p&gt;我们优化 prompt 的节奏大致分三步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;step1：根据 agent 的角色、功能，编写最初版 prompt&lt;/li&gt;
&lt;li&gt;step2：不断地增加 prompt 的内容，先把效果堆上来&lt;/li&gt;
&lt;li&gt;step3：效果达到预期后，再把 prompt 的部分内容存到 reference 中渐进式加载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个注意点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;prompt 无需关注工具的入参&lt;/li&gt;
&lt;li&gt;不要因为上下文长度，就无脑压缩 prompt&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;延伸阅读：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://platform.claude.com/docs/zh-CN/build-with-claude/prompt-engineering/claude-prompting-best-practices&quot;&gt;Claude 提示词最佳实践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/prompt-eng-interactive-tutorial&quot;&gt;prompt-eng-interactive-tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;上下文压缩&quot;&gt;上下文压缩&lt;/h3&gt;&lt;p&gt;上下文过长，一方面会拖慢模型的推理性能，另一方面也会影响推理效果。目前 AgentScope 默认的压缩策略有：压缩思考、压缩工具、模型回话。你可以这样理解它的演进：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;借助 AgentScope，默认会把模型的 thinking 过程和工具调用结果都压缩掉，每轮会话只保留用户和模型的 QA；&lt;/li&gt;
&lt;li&gt;更进一步的压缩策略，会保留一部分工具结果，同时删除一些无效的对话。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;三、状态机与-Plan-工具&quot;&gt;三、状态机与 Plan 工具&lt;/h2&gt;&lt;p&gt;一次运营活动往往复杂且流程冗长，既要灵活回答用户的问题，也要能引导用户按流程往下走。为此，我们在 system prompt 里动态注入了一套状态机，以最高优先级引导用户。这里有个关键细节：不要把状态机的规则写进 prompt，而是用 hard code 计算模型当前处于哪一步、下一步该做什么。 整体结构（示意）如下：&lt;/p&gt;
&lt;figure class=&quot;highlight json&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;&quot;currentStage&quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;阶段标识&quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;&quot;currentStep&quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;当前步骤标识&quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;&quot;nextStep&quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;下一步骤标识&quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;attr&quot;&gt;&quot;actionType&quot;&lt;/span&gt;&lt;span class=&quot;punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;independentThinking | callSkill | callAgent&quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;在明确当前步骤的同时，也要明确告诉模型：完成这一步应该独立思考、还是调用 skill、还是调用 agent。&lt;/p&gt;
&lt;p&gt;为了让步骤能动态流转，我们定制了一套 plan 工具：模型每完成一个步骤就调用它更新进度，系统据此感知内存数据变化，并在下一次 loop 时通过 middleware 把最新步骤喂回给模型。&lt;/p&gt;
&lt;p&gt;因为这些步骤都落到了数据库里，运营在前端就能直接看到流程进度，体感会好很多。&lt;/p&gt;
&lt;h2 id=&quot;四、主子-agent&quot;&gt;四、主子 agent&lt;/h2&gt;&lt;h3 id=&quot;什么时候用子-Agent？&quot;&gt;什么时候用子 Agent？&lt;/h3&gt;&lt;p&gt;先破除一个误区：agent 本质上只是对模型 API 的一层上下文封装，模型其实识别不了”子 agent”（对模型而言它只是一个 tool）。不过相比 skill，子 agent 有独立上下文、可并发执行等好处。所以要不要拆出独立子 agent，你可以用这三条标准来判断：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;子任务是否独立？&lt;/li&gt;
&lt;li&gt;子任务是否可以并行？&lt;/li&gt;
&lt;li&gt;子任务的迭代次数是否过多？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;【分工合作】还有一个容易被忽略的组织收益：当有多位算法同学参与时，按领域拆分子 agent，能让每个人对自己 agent 的效果负责——领域边界清晰后，联调更顺，整体项目效果也更有保障。&lt;/p&gt;
&lt;h3 id=&quot;主子-agent-的通信与协作&quot;&gt;主子 agent 的通信与协作&lt;/h3&gt;&lt;p&gt;这里有三条比较实用的经验：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在主 Agent 的 system prompt 里动态注入”当前要调用的子 Agent”，避免子 Agent 任务还没完成、主 Agent 就抢着接管，这能显著提升主 agent 的识别准确性；&lt;/li&gt;
&lt;li&gt;强制子 agent 用 json 返回内容，并加一个 &lt;code&gt;isCompleted=true&lt;/code&gt; 字段来表明自己已完成（部分模型可以通过参数开启结构化 json 输出）；&lt;/li&gt;
&lt;li&gt;子 agent 完成后，通过 middleware 机制把返回结果落库；因为 json 格式确定，这些数据可以直接作为后续工具的入参。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;推理时间过长的优化措施&quot;&gt;推理时间过长的优化措施&lt;/h3&gt;&lt;p&gt;项目初期测试时，你很可能会遇到 agent 的 reasoning 时间过长的问题。但请注意：别直接关掉 thinking，那会严重损伤效果。基于大模型，我们做了几项优化来降低 reasoning 时间：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;上下文压缩：去掉子 agent 的 skill-pre-load 机制，同时调低主 agent 的 thinking token limit；&lt;/li&gt;
&lt;li&gt;思考 budget：部分大模型提供 thinking-budget 参数，可以控制模型的思考时长；&lt;/li&gt;
&lt;li&gt;调整 top-p 和 temperature。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;还有几个方向我们还在持续探索：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;agent 并行调用；&lt;/li&gt;
&lt;li&gt;agent 异步调用，主 agent 提前把结果返回给业务；&lt;/li&gt;
&lt;li&gt;把 agent 的职责拆得更细，按职责为不同 agent 匹配不同参数的模型。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;五、工具与-MCP&quot;&gt;五、工具与 MCP&lt;/h2&gt;&lt;h3 id=&quot;如何保证接口参数的正确性&quot;&gt;如何保证接口参数的正确性&lt;/h3&gt;&lt;p&gt;对接营销投放平台、触达平台这类接口时，最让人担心的就是：模型理解不了参数字段的含义，进而传错参数，譬如渠道 ID、有效期、开始时间。&lt;/p&gt;
&lt;p&gt;事实也印证了这一点——初期联调时模型传错字段时有发生，典型的就是人群 SQL。原因在于：哪怕字段定义得再清楚，只要模型上下文里存在很多圈人的 SQL，它就可能随机选错（比如把正式表传成离线表、把权益的 SQL 用成触达的 SQL）。&lt;/p&gt;
&lt;p&gt;对此我们采取了三个措施：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在投放平台侧新增一个”参数定义”接口，严格限制入参枚举，并尽可能把定义写丰富；&lt;/li&gt;
&lt;li&gt;在运营平台侧，把投放平台的 MCP 封装成 tools，再通过 middleware 以 hardcode 的方式 extract 关键参数（人群 SQL、开始时间、有效期、活动类型等）——这些参数由代码直接传入，不需要模型推理；&lt;/li&gt;
&lt;li&gt;加监控：对”模型推理的参数”和”最终调用投放平台的参数”做两次落库，方便后续审计和追踪。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;工具并发调用踩坑&quot;&gt;工具并发调用踩坑&lt;/h3&gt;&lt;p&gt;并发调用工具能减少模型的 loop 次数、提升效果；但要小心：并发修改数据库字段时可能产生数据覆盖，破坏一致性。 这一点在设计并发方案时务必提前评估。&lt;/p&gt;
&lt;h2 id=&quot;六、联调与验证&quot;&gt;六、联调与验证&lt;/h2&gt;&lt;p&gt;改完 prompt 和 skill 后，验证对应 feature 往往非常耗时，而且你还很难确定改动是否波及了其他地方。&lt;/p&gt;
&lt;p&gt;我们的解法是定制一个脚本，基于编码 Agent 调用平台的 chat 接口，用模型与平台对话，并按约定的约束智能判断平台返回是否符合预期。整套验证会用到这几类 artifact：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个 e2e 脚本，通过编码 Agent 与平台对话；&lt;/li&gt;
&lt;li&gt;平台对话的输出；&lt;/li&gt;
&lt;li&gt;平台的运行时日志；&lt;/li&gt;
&lt;li&gt;平台模型的 context（只取最后两个数组）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把上述 artifact 喂给编码 Agent，让它按指示结合本次优化的 feature，分析平台表现是否达标。&lt;/p&gt;
&lt;p&gt;需要坦诚说明的是：这个思路目前还处在初期阶段，我们已经跑通了自动对话，但由于不同 agent 的能力都比较 professional，对话结束后仍然依赖算法同学结合日志来做最终分析。&lt;/p&gt;
&lt;h2 id=&quot;七、用户体验的一些措施&quot;&gt;七、用户体验的一些措施&lt;/h2&gt;&lt;h3 id=&quot;KeepAlive-保活机制&quot;&gt;KeepAlive 保活机制&lt;/h3&gt;&lt;p&gt;用户长时间不输入时，SSE 流很容易断掉。我们在服务端加了 keepalive 保活机制来避免会话中断——如果你的产品也用 SSE 做流式输出，这个几乎是必备项。&lt;/p&gt;
&lt;h3 id=&quot;断点重试机制&quot;&gt;断点重试机制&lt;/h3&gt;&lt;p&gt;网络抖动（譬如后台服务部署）会导致 chat 断开。为此我们在前端加了断点重试机制，会自动重试三次 chat 会话，尽量让用户无感。&lt;/p&gt;
&lt;h3 id=&quot;Ask-User-Question-卡片&quot;&gt;Ask User Question 卡片&lt;/h3&gt;&lt;p&gt;借助 AgentScope，我们把 ask user question 卡片做了优化：模型调用该工具后，用户可以直接选择、填空、完善日期等，交互体验更顺滑。&lt;/p&gt;
&lt;h2 id=&quot;八、未来发展-规划&quot;&gt;八、未来发展 &amp;amp; 规划&lt;/h2&gt;&lt;p&gt;最后，分享几个我们正在推进的方向，也欢迎你一起交流：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;多产品、多活动的 auto copilot；&lt;/li&gt;
&lt;li&gt;基于用户会话，沉淀不同类型活动的自动纠正机制：用定时任务扫描用户对话，捕捉用户质疑的内容，进而自动优化 skills 和 prompt；&lt;/li&gt;
&lt;li&gt;并发调用不同 agent，提高系统的响应速度。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item>
<item><title>系统架构设计师考试复盘：备考方法与资料</title><link>https://wxxlamp.cn/2026/07/12/soft-exam-system-architect-experience/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/07/12/soft-exam-system-architect-experience/</guid><pubDate>Sun, 12 Jul 2026 15:33:00 GMT</pubDate><description>记录三次软考高级系统架构师考试的心态变化与备考经验，梳理科目重点、刷题节奏、论文选题及实用资料。</description><content:encoded>&lt;h1 id=&quot;1-前言&quot;&gt;1. 前言&lt;/h1&gt;&lt;p&gt;24年的时候，因为公司“1+6+N”不当人的政策，让我因为一个月的误差，没有申请上杭州E类人才，错失了最后一波人才房上车的机会。&lt;/p&gt;
&lt;p&gt;后来打听到，软考高级+软著也可以申请杭州E类人才，所以就于24年上半年开始了漫漫的软考之旅。本来以为靠着河南人的刷题天赋，软考就是手到擒来，但是竟然还是陆陆续续考了三次才通过，所以特地分享下自己参加软考以来的心态波动和刷题技巧，希望对大家有所帮助。&lt;/p&gt;
&lt;h1 id=&quot;2-时间线&quot;&gt;2. 时间线&lt;/h1&gt;&lt;p&gt;笔者一共考了三次软考，分别是24年上半年、25年下半年和26年上半年。&lt;/p&gt;
&lt;p&gt;第一次是因为案例没有合格、第二次是论文没有合格，所幸最后一次全部通过：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/da5ed9d6_soft-exam-score-1.jpeg&quot; alt=&quot;IMG_8530.jpeg&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/c6c5bbc9_soft-exam-score-2.jpeg&quot; alt=&quot;IMG_8529.jpeg&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/ec2fcc20_soft-exam-score-3.jpeg&quot; alt=&quot;IMG_8528.jpeg&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;3-心态变化&quot;&gt;3. 心态变化&lt;/h1&gt;&lt;p&gt;笔者第一次考试是抱着必过的决心，买了软考高级的官方教辅资料，以及近年来的纸质试题，叠加公司内部的辅导课程。本来以为胜券在握，结果考到案例分析的时候，两眼懵逼，考了很多redis命令。像这种现用现查的东西，笔者自然是不记得了，果不其然，案例考了30分，挂科没商量。&lt;/p&gt;
&lt;p&gt;发现第一次没过之后，自然是十分伤心，但是考的题目确实不会，所以也没有办法。不过确实是赶不上杭州最后一波人才房了，蓝瘦香菇，看来命里无时莫强求，还是随缘去深圳好了。&lt;/p&gt;
&lt;p&gt;等到香港硕士读的差不多之后，笔者就又开始惦记软考这件事了。当时学校必修的课程基本都结束了，工作又不是太忙，所以就又花了一个月时间认真准备起了考试。因为上一次栽倒了案例分析上，所以这一次，特意把前三年的&lt;a href=&quot;https://www.yuque.com/wangxingxing-f4sey/vgwfvg/dts3v8qdk7gn2da9?singleDoc#%20%E3%80%8A%E6%A1%88%E4%BE%8B%E5%88%86%E6%9E%90%E3%80%8B&quot;&gt;案例分析真题&lt;/a&gt;都过了一遍。同时也整理了思维导图，用于综合知识的准备。&lt;/p&gt;
&lt;p&gt;第二次考试的真题说实话，不算太难，考完以为是稳过的结果，竟然棋差一招，栽倒了论文上。当年的论文是高并发系统设计，笔者自然是手拿把掐，没想到竟然只拿了38分。笔者实在不知道问题出在哪里，还申请了考试复核，结果自然是没有复核成功。因为实在是想不到问题出在哪里，笔者郁闷了一个月。后来想想，软考这种垃圾考试，审核结果不透明，出题内容过于讲话，真心不知道在AI时代有什么存在的必要。&lt;/p&gt;
&lt;p&gt;第三次考试，准备的十分匆忙，大概只有1周的时间。所幸靠着第二次考试准备的思维导图，没有花太多时间，就感觉掌握了差不多了。不过第三次考试的时候，心态反而没有前两次那么激进，因为此时软考证书和杭州E类人才就像鸡肋一样——食之无味、弃之可惜。而且本次备考的时间成本非常低，所以就抱着无所谓的心态进入考场。&lt;/p&gt;
&lt;p&gt;因为无所谓，甚至早上的综合知识考试都差点迟到了（还有2分钟停止进场）。结果考题都是自己复习过了，所以就顺理成章的过了~&lt;/p&gt;
&lt;h1 id=&quot;4-Q-A&quot;&gt;4. Q&amp;amp;A&lt;/h1&gt;&lt;h2 id=&quot;软考高级证书能做什么？&quot;&gt;软考高级证书能做什么？&lt;/h2&gt;&lt;p&gt;主要有两个作用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;软件行业以考代评，软考的高级证书相当于副高级的职称（主要看单位认不认），所以很多央国企的人会需要这个证书评职称；&lt;/li&gt;
&lt;li&gt;杭州地区可以通过软考高级证书+软著申请E类人才（以前能低价买政府共有产权房，现在只能领每月2,500的租房补贴）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;没有开发背景，能参加软考吗？&quot;&gt;没有开发背景，能参加软考吗？&lt;/h2&gt;&lt;p&gt;如果没有软件开发背景，还想要参加软考的话，可以报考项目管理类。&lt;/p&gt;
&lt;p&gt;其他类（如系统架构师）不建议&lt;/p&gt;
&lt;h2 id=&quot;软考要准备多久？&quot;&gt;软考要准备多久？&lt;/h2&gt;&lt;p&gt;对于日常有开发基础的人来说，如果能保证每个工作日学习2小时、周末5-8小时，一个月的时间足够了。&lt;/p&gt;
&lt;h2 id=&quot;软考系统架构师会考哪些内容&quot;&gt;软考系统架构师会考哪些内容&lt;/h2&gt;&lt;p&gt;系统架构师分为三科，分别是综合知识、案例分析、论文。&lt;/p&gt;
&lt;p&gt;综合知识考试的内容很杂，基本的网络工程、软件工程、操作系统、UML以及近两年兴起的生成式大模型，会涉及很多知识点，每个知识点基本就考1-2道题（一共75道选择题）&lt;/p&gt;
&lt;p&gt;案例分析的题型比较固定，第一题是固定的架构评估中的效应树；第2-5题则是4选2，其中有一道是软件架构、一道是多线程&amp;amp;redis。&lt;/p&gt;
&lt;p&gt;论文则是给四个论点，需要在2个小时内结合以往的项目经历，写够2500字的论文即可。&lt;/p&gt;
&lt;h2 id=&quot;有哪些参考资料？&quot;&gt;有哪些参考资料？&lt;/h2&gt;&lt;p&gt;笔者建议直接刷真题即可，大概刷3年的真题，综合知识和案例分析基本就差不多了（如果还考不够，只能说日常的积累不够）&lt;/p&gt;
&lt;p&gt;真题的话，有一个软件推荐给大家：软考真题（付费的），它会将相同知识点的真题汇总到一起，还是非常方便的。&lt;/p&gt;
&lt;p&gt;免费的网站有：&lt;a href=&quot;https://ruankaokao.com/exam/#/&quot;&gt;软考达人&lt;/a&gt; 等&lt;/p&gt;
&lt;h2 id=&quot;备考时间线怎么安排？&quot;&gt;备考时间线怎么安排？&lt;/h2&gt;&lt;p&gt;如果要备考一个月的话，建议前两周刷综合知识的真题并整理；第三周开始看案例分析的真题；最后一周准备论文模板即可。&lt;/p&gt;
&lt;h2 id=&quot;论文要怎么选题目？&quot;&gt;论文要怎么选题目？&lt;/h2&gt;&lt;p&gt;论文有四个题目供考生选择，考生只需要选择其中一个作答即可。&lt;/p&gt;
&lt;p&gt;选择论文题目的时候有两个策略：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先选会的，如果有多个题目都会，则&lt;/li&gt;
&lt;li&gt;选别人不会的。这是因为某个题目选择的人越多，评分的标准就越严格。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;5-个人笔记分享&quot;&gt;5. 个人笔记分享&lt;/h1&gt;&lt;p&gt;笔者在这三次备考期间，自己准备了很多资料，主要在复习的时候，我自己写了两个文档，供大家参考：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;XMind思维导图（综合知识和案例分析会用到）：&lt;a href=&quot;https://app.xmind.com/share/lE5J5S8W?xid=7B8EbZeJ&amp;amp;sheet-id=deeb2541dfbb6dbf95ebeadf3f&amp;amp;pwd=xL9LOkZ-&quot;&gt;https://app.xmind.com/share/lE5J5S8W?xid=7B8EbZeJ&amp;amp;sheet-id=deeb2541dfbb6dbf95ebeadf3f&amp;amp;pwd=xL9LOkZ-&lt;/a&gt; (Password: xL9LOkZ-)&lt;/li&gt;
&lt;li&gt;语雀笔记（记录了三年来的备考时间安排、论文模板）：&lt;a href=&quot;https://www.yuque.com/wangxingxing-f4sey/vgwfvg#&quot;&gt;https://www.yuque.com/wangxingxing-f4sey/vgwfvg?#&lt;/a&gt; 《软考系统架构师》&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item>
<item><title>一年又一年：2026 年高考作文随笔</title><link>https://wxxlamp.cn/2026/06/12/best-arrangement/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/06/12/best-arrangement/</guid><pubDate>Fri, 12 Jun 2026 16:00:00 GMT</pubDate><description>2026全国一卷高考作文</description><content:encoded>&lt;p&gt;2017 年 6 月的那天下午，我趴在课桌上，想睡却睡不着，只能睁着眼，看着窗边斜射的几缕阳光，透过玻璃打落在桌上和沙沙作响的纸笔上——内心有点激动，因为再过 20 分钟，我的高中生涯就彻底结束了。&lt;/p&gt;
&lt;p&gt;英语考试结束后，我和七位室友一起去了学校附近的一个网吧，第一次开了一个包夜。我本想发泄出高中三年所有的压抑和苦闷，但是考完试的我，仿佛一个泄了气的皮球，再也拿不出备考时的精力和劲头，草草玩到了凌晨一点，就顶不住困意，在椅子上慢慢睡去。&lt;/p&gt;
&lt;p&gt;那时候，我觉得我的人生刚刚开始，应该，再也不会这么累了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;我在初高中时期痴迷于漫画，立志将来要做一个能和画画打交道的工作。高考报志愿时，我尝试选择了建筑、风景园林等各种和”画画”擦边的专业，但都没被录取上。由于信息闭塞，我误以为和建筑沾边的专业都会涉及绘画。所以，我在报考学校的第三专业志愿里填了和建筑、土木有较大关联的”安全工程”。被录取之后，我一阵窃喜，后来到学校才发现，班里除了我之外，大部分人都是被调剂过来的。&lt;/p&gt;
&lt;p&gt;好在专业课里面有一门叫”机械制图”的课，需要各种画图，我很喜欢，也听得很仔细。那一学期，这门课我拿了满绩。&lt;/p&gt;
&lt;p&gt;那时候，我以为生活和学习像这样按部就班，就可以了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;也正因为我上课听得仔细，无意中听到了专业课老师在课堂上”唱衰”安全工程这个专业——他对这个专业的发展和未来就业不抱希望。恰好当年我有转专业的资格，再加上 18 年互联网风口正盛，所以即使对电脑的操作仅限于开关机和 4399 的水平，我还是毅然选择了转去计算机类专业。&lt;/p&gt;
&lt;p&gt;到了新专业之后，我学得很吃力，一度想转回原来的专业——那里有我熟悉的领域和熟悉的朋友。但是开弓没有回头箭，周末两天泡在图书馆硬啃 C 语言，终于在期末顺利地坚持了下来。恰逢学院做网络安全相关的学科建设，虽然不甚了解，我还是报名参加了 Web 安全的培训，沉浸在各种 CTF 中被大佬带飞。&lt;/p&gt;
&lt;p&gt;那时候，我想象教我们的那些白帽子一样，在各种比赛中崭露头角，在网络世界里随意穿梭。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;持续了将近一年后才发现，由于自己计算机基础太过薄弱，没办法一蹴而就，没有正反馈，也实在体会不到其中的乐趣。思虑再三后，还是退出了学院的网络安全实验室。恰好遇到一位老师对编程语言讲得十分透彻，周围也有好朋友想借此深入学习，方便将来找工作，我便也加入其中，开始没日没夜地学习后端开发。&lt;/p&gt;
&lt;p&gt;但因为自己对保研还心存幻想，中间也和其他同学参加过一些比较热门的大数据比赛。虽然没掌握核心技术，但是组里有一位 PPT 大神，再搭上带队老师的东风，没想到几经波折，竟然进入了决赛。&lt;/p&gt;
&lt;p&gt;此时一方面是保研，一方面是就业，没有人告诉我应该怎么选择。后来盘点后发现，保研几率太小，再加上和之前网络安全一样，我始终不认为参加这些比赛能够增长能力，所以实在没有兴趣做这些东西。即使拿到了一些证书，也还是草草收场。自那以后，不再关心比赛，便持续地投入在后端开发的学习上。&lt;/p&gt;
&lt;p&gt;那时候，我觉得读研无望，只能面向工作编程，希望将来能找到一个好工作。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;事实证明，持续地投入一件事，是会有收获的。20 年春，凭着还算可以的经历，我拿到了很多公司的实习 offer，顺利入职。不久后，我也成功转正，毕业后真的进入了一家互联网公司工作。公司的同事们对我都很好，有很多同龄人，一起上班、一起工作、一起学习、一起吃饭，有时候还会一起熬夜打游戏。&lt;/p&gt;
&lt;p&gt;我还记得 HR 面试的时候问我的未来规划，我说：”我希望入职后好好学习和工作，希望 x 年内独当一面，y 年内成为领域专家，z 年内能够在业界有好的专业声誉。”HR 听完后笑了笑，就给我通过了。&lt;/p&gt;
&lt;p&gt;那时候，我以为只要好好学习，认真工作，就会有收获。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;每个行业都有自己的生命周期，土木是这样，互联网也不例外。22 年各家互联网公司心照不宣地开始裁员，看着我以前的老板、同事、朋友或主动或被动地不断离开公司，我内心惶恐不安。公司的经营策略发生变化，员工就像是工具一样，不论工具是否优秀，只要不合适，就会被随手扔掉。好在自己年轻，薪资不高，躲过了这波大清洗。但裁员过后，原有的晋升路径彻底改变，一地鸡毛，我在公司的职业轨迹也和规划中的渐行渐远。&lt;/p&gt;
&lt;p&gt;人生路上，总是要有一支火把，紧紧抓住它，才能看清方向。我选择了停下来，去香港读研，体验一下不一样的风土文化。在深圳和香港通勤的那两年里，见到了很多不曾想过的事情，生命的厚度，仿佛又延展了一些。&lt;/p&gt;
&lt;p&gt;那时候，我知道，时代的一粒沙，落在一个人身上，就是一座山。面对风云变幻的世界，我只能接受，然后再做出改变。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;其实人生是在不断变化的，环境也是不断变化的，当然人也是不断变化的。在时代的洪流中，面对这些变化，我别无选择，只能顺应这些变化，成为一个新的自己。一切，都是最好的安排。&lt;/p&gt;
</content:encoded></item>
<item><title>Qoder AI 编程实践：万行代码开发的收益与边界</title><link>https://wxxlamp.cn/2026/06/07/qoder-vibe-coding-thoughts/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/06/07/qoder-vibe-coding-thoughts/</guid><pubDate>Sun, 07 Jun 2026 08:00:00 GMT</pubDate><description>通过两个大型商业项目的实践，分享使用Qoder完成1万行代码开发的经验与思考，探讨AI Coding的效能边界与程序员的能力演进方向。</description><content:encoded>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;&lt;p&gt;以前使用Claude Code作为代码开发工具，但前段时间因为各种原因用不了Opus系列模型了，加上公司推广，就用上了平替版的Qoder来开发代码。&lt;/p&gt;
&lt;p&gt;4月期间开发了将近1万行有效业务代码，在开发阶段人工的干预率不超过10%，但联调和提测由于基建的原因，人工干预偏多。&lt;/p&gt;
&lt;p&gt;本文会通过两个重点项目的实践，来聊聊基于coding agent，我们的研发效能到底能提高多少，以及在全员AI coding的大背景下，思考我们究竟还有什么值得关心的事情。实践相关的内容比较长，赶时间的读者可以直接跳到感悟部分。&lt;/p&gt;
&lt;p&gt;笔者在此简单记录coding阶段的实践和思考，同时也相信，这些经验不止适用于Qoder，也希望沉淀出一些通用策略，以便在其他coding agent上复用。&lt;/p&gt;
&lt;h1 id=&quot;实践&quot;&gt;实践&lt;/h1&gt;&lt;h2 id=&quot;【实践一】使用Qoder完成网关和外部渠道的支付流程对接&quot;&gt;【实践一】使用Qoder完成网关和外部渠道的支付流程对接&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;本实践是在已有的成熟架构模式下，通过渠道成熟的接口文档，完成支付流程总共5个接口、2个webhook的对接&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;开发准备阶段&quot;&gt;开发准备阶段&lt;/h3&gt;&lt;p&gt;在开发准备过程中，由于PRD并不是AI原生的（即没有按照AI能理解的结构化方式编写），所以如果直接把PRD喂给AI的话，即使系统的skills、mcp、rules非常完备，Qoder写出来的毫无疑问也是一坨。所以在开发之前，我作为人工参与方主要做了下面几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;渠道接口澄清：&lt;/strong&gt; 弄清楚渠道接口的调用流程和时序、重试、幂等等特殊case；同时准备渠道完备的接口文档以供后续Qoder理解；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;业务方案澄清：&lt;/strong&gt; 由于PRD在评审完成之后，还有很多待定点以及和现有业务冲突的地方，必须人工介入和PD、业务对齐整体需求；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技术方案澄清：&lt;/strong&gt; 即使可以让AI编写技术方案，但由于基建问题，Qoder并不清楚渠道接入的设计偏好、侧重点。可以预见，如果直接让Qoder根据PRD和渠道文档编写技术方案，势必会造成大量纠正和返工。从成本角度考虑，不如由开发自己编写技术文档的骨架和注意点，然后再交由Qoder完善；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存量代码梳理：&lt;/strong&gt; 对于网关来说，接入渠道是一项基本能力，架构非常成熟，所以除了技术方案、PRD、渠道接口外，还应该提供给Qoder系统已有渠道的存量接入代码（如任务调度策略、接口渲染策略等）。不能指望Qoder在没有上下文的情况下自己grep代码理解——这种方式一定有遗漏。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;AI-Coding阶段&quot;&gt;AI Coding阶段&lt;/h3&gt;&lt;p&gt;在AI coding之前，我先根据以往的接渠道经验，生成了一个对接新渠道的skill。再通过该skill结合SPEC方式，来完成AI coding的设计，包含方案澄清、代码实现、测试验证和编译完成。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/88836131_qoder-1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;在这个过程中，Qoder会抓取语雀链接、渠道接口文档，以及根据技术文档获取仓库中相关的代码。因为技术方案比较清晰，这一阶段的思考和产出正确率都非常高。之后，Qoder便会针对技术方案中的模糊点生成提问：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/b74e18a5_qoder-2.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;在问题澄清之后，Qoder便会生成具体的SPEC文档。此时花费了大概1小时来对文档进行review，并且纠正错误设计，同时让Qoder重新优化SPEC文档：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/675c3786_qoder-3.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当SPEC文档澄清完成后，Qoder便可以进入自主的规划、实现、编译和自测阶段。&lt;/p&gt;
&lt;p&gt;&lt;u&gt;在Coding的过程中，虽然需要不断澄清Qoder的设计，但有Qoder参与后，确实可以帮忙考虑到技术文档设计阶段难以考虑到的事情。例如新渠道token的刷新机制，就是在Qoder的提问和引导下才发现原来的技术设计文档有误，进而重新优化的。&lt;/u&gt;&lt;/p&gt;
&lt;h3 id=&quot;Code-Review阶段&quot;&gt;Code Review阶段&lt;/h3&gt;&lt;p&gt;AI Coding完成后，即使多次review过SPEC文档，也需要更具体地基于代码进行一轮CR。&lt;/p&gt;
&lt;p&gt;这里我采用了两种方式，一是采用独立上下文的CR agent结合技术文档进行CR，另一个就是人工肉眼check代码。整体耗时大概2小时。在CR阶段发现了几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Qoder的编码习惯难以match我个人的开发习惯，导致有些代码风格会发生变化。譬如在和渠道交互的时候，所有的出入参全都用&lt;code&gt;json object&lt;/code&gt;包裹，虽然对AI来说理解起来没什么难度，但后续的维护可能是一个大问题；&lt;/li&gt;
&lt;li&gt;Qoder对当前应用的&lt;strong&gt;架构理解&lt;/strong&gt;还存在欠缺，导致对一些架构相关的基础代码编写处理还比较欠妥；&lt;/li&gt;
&lt;li&gt;因为应用中采用了大量的diamond、数据库配置，但&lt;strong&gt;Qoder又很难精准拿到（或者新增）相关的配置信息&lt;/strong&gt;，导致在配置处理上存在很多异常的问题；&lt;/li&gt;
&lt;li&gt;同时，基于上面的问题，如果一味地提供prompt要求AI修改，&lt;strong&gt;可能因为上下文污染，导致Qoder越修越错&lt;/strong&gt;，最后不得不人工下场干预。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;上面的问题，理论上都可以通过harness工程完善来提高代码的AI理解力。对于问题4来说，可以尝试使用subagent来降低上下文占用导致的幻觉。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;自测-fix阶段&quot;&gt;自测&amp;amp;fix阶段&lt;/h3&gt;&lt;p&gt;当代码CR完成后，便开始进入自测阶段。理论上自测也可以通过配置浏览器、HSF、数据库、SLS各种MCP完成，但因为基础设施并没有太完善，所以仍然遵循手工自测的方式。&lt;/p&gt;
&lt;p&gt;在自测阶段，发现了几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;网关应用在对接渠道时，即使给到渠道的标准化对接文档，但仍然没有合理解析渠道的出入参。譬如渠道的出参是&lt;code&gt;data.response.result&lt;/code&gt;，Qoder编写的代码解析的时候，硬生生遗漏了中间的response，解析成了&lt;code&gt;data.result&lt;/code&gt;，这就完全获取不到渠道的信息了。&lt;/li&gt;
&lt;li&gt;另外和渠道对接的时候，要重点关注渠道的错误码以及相关的幂等重试机制。Qoder根据渠道的技术文档只处理了返回错误的情况，但并没有考虑到错误后的重试、以及如何恢复的case。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;u&gt;面对这几个问题的修复，其实很难再在之前的上下文中通过prompt让Qoder修复。因为有一个尴尬的问题：很多联调出来的bug，需要反复和渠道对焦才能得到处理方案。在这一阶段，经过实践发现，人工修改可能会比AI Coding快得多。&lt;/u&gt;&lt;/p&gt;
&lt;h2 id=&quot;【实践二】使用Qoder完成未知代码的资金结算对接&quot;&gt;【实践二】使用Qoder完成未知代码的资金结算对接&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;本实践是在开发者对所开发应用不甚了解的情况下，使用Qoder基于SPEC完成代码编写的过程。代码100%由AI编写完成，整体交付时间在一周左右。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;开发准备阶段-1&quot;&gt;开发准备阶段&lt;/h3&gt;&lt;p&gt;和【实践一】的准备阶段一样，虽然不了解开发的应用架构，但还是基于笔者自己的上下文写了一份技术文档，然后再输入给了Qoder+SPEC进行设计和编码。&lt;/p&gt;
&lt;p&gt;编写技术文档，一方面是给Qoder阅读，更精确地match上开发者的思路；另一方面，也是笔者对系统的一个熟悉过程。如果开发者自己都不熟悉系统，100% AI Coding对于现阶段来说显然是一个极难实现的目标。&lt;/p&gt;
&lt;h3 id=&quot;AI-Coding阶段-1&quot;&gt;AI Coding阶段&lt;/h3&gt;&lt;p&gt;人工完成核心技术文档编写：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/0d21480a_qoder-4.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;在SPEC阶段，经过Qoder的提示，发现之前技术方案的设计有重大问题：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/0cfc6174_qoder-5.png&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;因为本次需要新增税金数据，按照原来业务代码的恒等式逻辑，本次需要在模型层新增两个字段：税金大小和税金币种；&lt;/li&gt;
&lt;li&gt;同时，因为笔者原来对相关代码不甚了解，在SPEC的过程中，Qoder也帮笔者指出了税金资金流的流入和流出方向，帮助笔者少踩了许多坑。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Code-Review阶段-1&quot;&gt;Code Review阶段&lt;/h3&gt;&lt;p&gt;Qoder自主完成代码编写和单元测试后，笔者便另起了一个新的agent协助完成CR。在这一过程中，发现了国家校验相关的问题（只有墨西哥能计税，其他非墨西哥流量应该报警并拦截）。由于整体代码量不高（500行左右），CR和CR fix都是由新的agent结合技术文档自主完成的。&lt;/p&gt;
&lt;h3 id=&quot;自测-fix阶段-1&quot;&gt;自测&amp;amp;fix阶段&lt;/h3&gt;&lt;p&gt;在coding阶段结束后，便进入到整体链路测试阶段。在这一过程中，通过走单发现了一些数据不一致的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CASE1：&lt;/strong&gt; 订单结算时落库报错，发现代码的字段和数据库新增的字段不一致。——配置人工变更，和Qoder理解有出入，导致数据库insert失败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CASE2：&lt;/strong&gt; 在订单部分退款后重新计税，发现结算总金额有误差导致无法结算。人工排查原因后发现，在退款重新计算结算金额的时候，没有处理新增税金的字段。——属于知识库和模型问题导致。只能提示Qoder完成相关代码的补充编写：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/a6f6e234_qoder-6.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从遇到的问题中可以发现，Qoder一方面因为上下文不够、思考过程信息不足，导致技术方案出现偏差。另一方面，遇到非代码问题时，还是需要人工干预和对齐，否则Qoder的代码编写也会出现失误。&lt;/p&gt;
&lt;h1 id=&quot;感悟&quot;&gt;感悟&lt;/h1&gt;&lt;h2 id=&quot;善用SPEC模式&quot;&gt;善用SPEC模式&lt;/h2&gt;&lt;p&gt;Qoder的SPEC模式非常强大，只需要在对话框中启用SPEC即可。Qoder底层会通过planning、subagent模式结合已有的mcp和skill等工具先进行Q&amp;amp;A，最后再生成spec文档。&lt;/p&gt;
&lt;p&gt;用户需要重点review spec文档，反复通过对话的形式进行spec文档的修正，进而提高文档的准确性，保证代码的交付质量。&lt;/p&gt;
&lt;p&gt;从交付效果上看，使用SPEC+仔细的代码review会比简单的对话式coding效率和效果都强得多（至少2倍）。&lt;/p&gt;
&lt;p&gt;不过有一个问题在于Spec模式的上下文占用可能比较快，不确定多次压缩后效果会不会下降，但目前看起来Qoder的压缩能力还可以，整体准确度并没有明显衰减。&lt;/p&gt;
&lt;h2 id=&quot;上下文决定一切&quot;&gt;上下文决定一切&lt;/h2&gt;&lt;p&gt;虽然现代的coding agent都具有上下文压缩的能力，但我们也不能无限制地在一个session中完成所有需求。&lt;/p&gt;
&lt;p&gt;Claude Code的官方文档中就有提到，我们要尽可能地节省上下文，上下文占用率小于50%的效果远高于上下文占用率超过70%的效果。&lt;/p&gt;
&lt;p&gt;所以，上下文永远是珍贵的资源，我们可以通过如下方式来节约上下文：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;使用subagent：&lt;/strong&gt; subagent是独立于主agent的上下文，主agent只需要关心subagent的结果，而无需关心过程。如果读者细心check Qoder的执行过程，就会发现Qoder自己有时候会自发地采用subagent做一些处理；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用skill而非mcp：&lt;/strong&gt; mcp和skill都会占用Qoder的上下文，但mcp只是告诉模型有这么一个工具，远不如skill这种SOP来得准确；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;禁用非必须的skill和mcp：&lt;/strong&gt; 不要把系统和个人配置的所有mcp和skill在每一个会话中都加载，这样既占用上下文，也会给模型造成选择负担。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;同时，我们要利用Qoder的repowiki功能尽可能更新系统的整体架构、利用rule和hook做一些规则上的更新和处理。repowiki和rule都会在每次会话中作为上下文输入给模型，只有不断地更新这些数据、持续维护harness工程，才能保证Qoder越用越好用。&lt;/p&gt;
&lt;h2 id=&quot;非代码配置影响AI-Coding效率&quot;&gt;非代码配置影响AI Coding效率&lt;/h2&gt;&lt;p&gt;在手工编程的时代，所有人都在通过非代码的配置化来提高研发效能。当老板问”你如何证明系统的研发效能得到提高？”，我想大部分人的答案中可能都包括”以前用代码，现在配置化就能完成”。&lt;/p&gt;
&lt;p&gt;除此之外，众所周知的原因，目前应用的部署和发布流程极其复杂，通过配置化可以尽可能地让变更快速上线。但是，在AI Coding时代，当所有代码都可以用AI几乎瞬间编写完成，那些散落在diamond、switch、数据库的配置还能提高研发效能吗？&lt;/p&gt;
&lt;p&gt;不仅如此，这些散落在各处的配置，反而给AI的理解造成了各种卡点。就目前的基础设施而言，AI可以快速读到代码的所有内容，但一旦涉及到配置，AI就傻眼了——它很难直接读取配置，也就容易写出各种奇怪的代码。同时，因为没有配置权限，这些配置也要花费大量的时间由开发者自行处理。&lt;/p&gt;
&lt;h2 id=&quot;人工参与不可避免&quot;&gt;人工参与不可避免&lt;/h2&gt;&lt;p&gt;很多人会从架构和顶层设计中，重新思考人和AI的定位。极端的顶层设计甚至会认为不需要人工，全部通过AI做需求分析、coding、测试和上线。这确实是一个美好的愿望，但基于目前的一线实践来看，在大型商业系统中，要达到全链路AI可能还需要些时日。简而言之，目前的AI Coding依旧需要专家经验和人工干预。&lt;/p&gt;
&lt;p&gt;具体有以下几点感悟：&lt;/p&gt;
&lt;p&gt;首先，毫无疑问的是，简单的、类级别改动的需求，可以完全交给AI没有任何问题。&lt;/p&gt;
&lt;p&gt;但有一个重要问题：人在需求设计的时候没办法完全阐述清楚需求。不仅是PD，也包含开发。即需求创建方无法一次性把所有需要交付的需求讲述清楚。在这个前提下，也不能要求AI直接写出还原度高、健壮性好的代码。简单来说，目前AI coding只能介入到研发流程的每个原子阶段，通过Vibe Coding的形式完成代码迭代，但对于整套软件生命周期来讲，还是需要人工来将其串起来。&lt;/p&gt;
&lt;p&gt;另一个点，就是老生常谈的协作问题。人们往往说，研发的大部分时间都是在开会而不是写代码。同样的，在软件研发的流程中，大部分时间不是在写代码，而是在联调。从深度使用Qoder的两次实践来看，Qoder能在第一次SPEC阶段完成将近85%的代码编写，但后面需要联调fix的15%的代码，却必须要人工介入，Qoder无能为力。&lt;/p&gt;
&lt;p&gt;使用Qoder完成渠道对接的时候，笔者就发现，Qoder忽略了很多防御性编程需要的内容（幂等、重试、事务、一致性等），这些内容需要针对不同的系统架构做不同的定制，目前在开发过程中还是需要开发同学深度介入，才能让Qoder写出符合预期的代码。另一方面，这倒不是一个不可解的问题，通过不断基于harness工程完善不同项目的编码习惯和规则，这种case的人工介入会越来越少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最后想说的是，既然目前人工参与不可避免，那就仍然要求开发人员理解整体的需求、方案设计、代码细节。否则，你敢让一个你不了解的代码上线服务用户、操作资金吗？&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;程序员的能力边界在哪里？&quot;&gt;程序员的能力边界在哪里？&lt;/h2&gt;&lt;p&gt;假如说，上文中提到的、影响AI全链路coding的卡点（模型能力、上下文、基础设施）都被解决了，那么程序员的Job Model是否会改变呢？&lt;/p&gt;
&lt;p&gt;答案是肯定的。&lt;/p&gt;
&lt;p&gt;首先，程序员不会再拘泥于编程语言，今天可以用Java、明天可以用Python。只要掌握了编程语言的核心思想，语法和生态完全可以由模型来帮助完成。另外，程序员也不用拘泥于工种，不管是前端后端还是大数据，理论上全都可以在AI的辅助下完成。&lt;/p&gt;
&lt;p&gt;这样一来，仿佛程序员啥都能干了？AI的到来，看起来让我们对程序员的要求也降低了。其实笔者反而认为恰好相反。AI让程序员不再拘泥于需要死记硬背的八股文和语法，而是需要核心聚焦在那些能迁移的能力上。譬如：学习力、创新力、快速迁移的能力。程序员需要了解的东西也更多了，需要在核心领域上帮助AI做决策、做判断、做指导，而不能只是基本的实现——因为实现这件事，一定是AI做的。&lt;/p&gt;
&lt;p&gt;在不远的将来，程序员一定要像掌握计算机基础八股文一样，掌握各种Coding Agent的工具，把代码开发交给AI，然后往更上一层拓展自己的能力边界，深度介入业务的需求和产品能力，才是确定性最高的趋势。&lt;/p&gt;
&lt;p&gt;所以，如果你觉得目前的工作没有需要你结合专业知识进行判断的地方，一直都是埋头开发，那么，是时候抬起头，重新看看自己了。&lt;/p&gt;
</content:encoded></item>
<item><title>Claude Code 使用指南：命令、Skill 与 Subagent</title><link>https://wxxlamp.cn/2026/04/13/claude-code-command-guide/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/04/13/claude-code-command-guide/</guid><pubDate>Mon, 13 Apr 2026 14:00:00 GMT</pubDate><description>深入解析Claude Code的核心概念与常用命令，涵盖Command、Skill、Rule、Subagent等关键功能的详细介绍与使用技巧，助您掌握现代化AI编程工作流。</description><content:encoded>&lt;p&gt;Work agent在25年风头无两，其中以Claude Code最为显眼。Claude Code一直走在coding agent的最前列，提出了skills、subagent等多种已经成为事实标准的概念。&lt;/p&gt;
&lt;p&gt;随后一系列的Coding Agent，如Qoder、Gemini Cli、Qwen Cli、CodeX全部follow CC的主要设计，只要能针对一个agent的原理有所理解，对于其他agent也可以达到一通百通的程度了。&lt;/p&gt;
&lt;p&gt;同时，因为commands、skills、rules、subagent这些概念基本上在每个work agent都有存在，所以一旦这些资产沉淀下来，其他的agent也可以顺利复用。&lt;/p&gt;
&lt;p&gt;所以依据claude code 的官方文档，加上自己的理解，特意梳理了下相关的slash概念，帮助后续更好的vibe coding。&lt;/p&gt;
&lt;p&gt;本文是Claude Code系列的第一篇，介绍基础的概念和操作——预计还有两篇，介绍CC的常见的上下文优化手段与我个人对辅助CC编程的一些思考。&lt;/p&gt;
&lt;h1 id=&quot;基本概念&quot;&gt;基本概念&lt;/h1&gt;&lt;h2 id=&quot;Command&quot;&gt;Command&lt;/h2&gt;&lt;p&gt;command是最直接驱使agent的user prompt。通过直接在CLI或者GUI的输入框输入指令，便可触发CC完成响应。&lt;/p&gt;
&lt;p&gt;另外如果指令太长，或者指令可以复用，则可以把指令形成文件，放置到 .cluade/commands/ 目录下面，这样在下次使用的时候，在CLI中直接 输入 slash + command 即可，如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/94ab8801_b76cb1b4.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;Skill&quot;&gt;Skill&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;相关内容参考：&lt;a href=&quot;https://code.claude.com/docs/zh-CN/skills&quot;&gt;https://code.claude.com/docs/zh-CN/skills&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Skill可以理解是一个大号的command，把一些复杂或者标准工作沉淀成一套SOP。不过，skill与command最大的不同就是，skill不仅可以主动调用，也可以在vibe coding的时候，claude code可以根据上下文信息分析是否要自动调用skill。那么，如何编写一个skill呢？&lt;/p&gt;
&lt;p&gt;人工构建skill指令当然是好的，但是AI时代，我们也可以让CC帮我们完成skill的编写，这里可以使用CC官方的&lt;a href=&quot;https://code.claude.com/docs/zh-CN/skills&quot;&gt;skill指南&lt;/a&gt;和&lt;a href=&quot;https://github.com/anthropics/skills/blob/main/skills/skill-creator/SKILL.md&quot;&gt;skill 指令&lt;/a&gt;来帮我们创建skill。&lt;/p&gt;
&lt;p&gt;有了创建skill的skill之后，就可以通过对话的形式，让claude code帮我创建一些其他skill。举个例子，我希望创建一个获取yuque.com的文档，prompt如下：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;帮我创建一个skill，作用是获取yuque.com中的文档，具体是我给你一个yuque的文档链接（这个链接我可以在chrome中正常访问，因为我在chrome中有登录语雀账号，链接也是我自己的语雀文档链接），你帮我把语雀连接中的文档读出来，并下载到本地，以markdown格式保存&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;可以发现，claude code可以自动发现我们刚才写的&lt;code&gt;skill-create&lt;/code&gt;skill，如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/2b4cdca1_dcdc8b25.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;具体生成的skill我已经上传到github上了，感兴趣可以&lt;a href=&quot;https://github.com/wxxlamp/wxxlamp.github.io/blob/deploy/.claude/skills/yuque-fetcher/SKILL.md&quot;&gt;点击&lt;/a&gt;获取。&lt;/p&gt;
&lt;p&gt;下面，基于用skill-create生成yuque-fetcher skill，来将一篇语雀文档生成到本地，claude code会遍历所有可用的skill，然后默认调用yuque-doc-downloader skill，进行语雀文章的下载&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/b62727ad_4db8f1a8.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;结果也是非常的哇塞，虽然图片没有顺利展示，但是基本的的内容都有了。&lt;/p&gt;
&lt;p&gt;有个问题是，怎么发现自己的skill是否被调用呢？可以通过~/.claude.json来check skill被后台调用的次数：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/006b47f7_c3232358.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;Rule&quot;&gt;Rule&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;相关内容参考：&lt;a href=&quot;https://code.claude.com/docs/zh-CN/memory#&quot;&gt;https://code.claude.com/docs/zh-CN/memory&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;rule的本质也是一种记忆，cc在每次请求模型的时候都会把完整的rule带给LLM，我们可以通过 /memory 命令进行check：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/c09f471b_3f295084.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;rule最常见的使用就是限制claude做一些事情，譬如说最常见的代码规范，我们可以把一些规约性的内容编写成rule提供给CC作为参考。项目级别的rule的位置如下：&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;your-project/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;├── .claude/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   ├── CLAUDE.md           # 主项目指令&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│   └── rules/&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│       ├── code-style.md   # 代码样式指南&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│       ├── testing.md      # 测试约定&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;│       └── security.md     # 安全要求&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;值得注意的是，我们可以通过path属性选择rule对哪些文件生效，如下所示：&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;---&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;paths:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  - &quot;src/api/**/*.ts&quot;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;---&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;API 开发规则&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;- 所有 API 端点必须包括输入验证&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;- 使用标准错误响应格式&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;- 包括 OpenAPI 文档注释&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Hooks&quot;&gt;Hooks&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;相关内容参考：&lt;a href=&quot;https://code.claude.com/docs/zh-CN/hooks-guide&quot;&gt;https://code.claude.com/docs/zh-CN/hooks-guide&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hook机制使得用户可以更加精准掌控claude code的行为，我们可以在会话前后、脚本执行前后执行各种命令。&lt;/p&gt;
&lt;p&gt;根据&lt;a href=&quot;https://code.claude.com/docs/zh-CN/hooks-guide&quot;&gt;cc的官方文档&lt;/a&gt;，可以返现常见的hook周期如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/0700f2c0_ab484b5d.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;比较经典的hook场景有上下文压缩，我们可以针对某个工程，在每次claude会话关闭的时候都让claude压缩一次上下文存储到本地，然后在下次claude启动的时候再自动读取。&lt;/p&gt;
&lt;p&gt;可以参考&lt;a href=&quot;https://code.claude.com/docs/zh-CN/hooks-guide#%E5%9C%A8-claude-%E9%9C%80%E8%A6%81%E8%BE%93%E5%85%A5%E6%97%B6%E8%8E%B7%E5%BE%97%E9%80%9A%E7%9F%A5&quot;&gt;claude code的官方例子&lt;/a&gt;，在每次需要人工介入的时候，弹出一个提示，防止任务一直holding：&lt;br&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/799f3c38_07e56628.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;MCP&quot;&gt;MCP&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;相关内容参考：&lt;a href=&quot;https://code.claude.com/docs/zh-CN/mcp&quot;&gt;https://code.claude.com/docs/zh-CN/mcp&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;claude在接收到指令的时候，不止会列出所有的skills、rules，还有所有的mcp，Claude Code会根据用户的指令，选择最合适的MCP执行。&lt;/p&gt;
&lt;p&gt;不过需要注意的是，mcp的地址与其他诸如skills之类的不一样，用户级别和项目级别的mcp的配置均在 ~/.claude.json中，常见的MCP如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;用途说明&lt;/th&gt;
&lt;th&gt;主要功能&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;sequential-thinking&lt;/td&gt;
&lt;td&gt;用于复杂问题的顺序推理。当需要解决多步骤问题、进行逻辑分析或规划复杂任务时，该服务器帮助逐步构建推理链。&lt;/td&gt;
&lt;td&gt;多步骤逻辑分析、任务规划、逐步推导结论&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jetbrains&lt;/td&gt;
&lt;td&gt;与 JetBrains IDE（如 IntelliJ IDEA、PyCharm、WebStorm 等）集成。&lt;/td&gt;
&lt;td&gt;允许 AI 直接读取代码、执行重构、运行测试、访问项目结构等 IDE 级操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;browsermcp&lt;/td&gt;
&lt;td&gt;实现浏览器自动化操作，AI 可控制浏览器行为。&lt;/td&gt;
&lt;td&gt;页面导航、点击元素、填写表单、截图；适用于网页测试、数据抓取、应用调试&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;puppeteer&lt;/td&gt;
&lt;td&gt;基于 Puppeteer 框架的浏览器自动化，功能与 browsermcp 类似。&lt;/td&gt;
&lt;td&gt;页面控制、元素操作、执行 JavaScript、生成 PDF/截图；更底层、可编程性强&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;context7&lt;/td&gt;
&lt;td&gt;提供对最新编程文档和代码示例的实时访问，超越 AI 训练数据的时效性限制。&lt;/td&gt;
&lt;td&gt;查询 API 用法、获取最新库文档、检索代码示例、支持版本更新内容检索&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;注：browsermcp 与 puppeteer 功能重叠，但 browsermcp 为通用浏览器 MCP 接口，puppeteer 通常特指基于 Chromium 的 Node.js 自动化框架实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当然mcp也可以配置在项目级别，只需要使用如下命令即可：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;claude mcp add --transport http hubspot --scope user https://mcp.hubspot.com/anthropic&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Subagents&quot;&gt;Subagents&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;相关内容参考：&lt;a href=&quot;https://code.claude.com/docs/zh-CN/sub-agents&quot;&gt;https://code.claude.com/docs/zh-CN/sub-agents&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不同的agent可以使用不同的skill和MCP、有不同的权限、模型和上下文。个人认为，subagent最重要的一点就是上下文的隔离，可以把不同任务用上下文隔离开。这样就会节省很多token，使得模型的回答更加精确。&lt;/p&gt;
&lt;p&gt;同时，subagent既然叫做agent，就说明他和主agent的能力几乎一样，subagent也有一块独立的memory空间供其维护长期的上下文，我们可以在创建subagent的时候，显示开启memory存储。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/d98bd464_7ce7a25c.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;如何唤醒subagent呢？有两种方式，一种是在cli命令中使用自然语言，如&lt;code&gt;使用code reviewer 帮我完成cr&lt;/code&gt;或者使用&lt;code&gt;@&lt;/code&gt;强制要求Claude code使用某agent，如 &lt;code&gt;使用@‘code reviewer(agent)’帮我完成cr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;当我们创建完subagent之后，它的存储地址和skills、commands一致。如果是项目级别的话，则会存储到&lt;code&gt;project/.claude/agents/&lt;/code&gt;目录中&lt;/p&gt;
&lt;p&gt;举个例子，我们可以创建一个 &lt;code&gt;code-reviewer&lt;/code&gt;的subagent，帮我们独立于主上下文来review代码。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/93981e05_7290ccbd.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看出，系统可以根据自然语言选择出对应的subagent来操作。不过需要注意的是，既然系统可以自动抓取要使用的subagents，那就说明subagents也在memory中占有一席之地。换句话说，subagent也不是越多越好，太多了容易和mcp一样，把上下文的token撑爆。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/5e29caec_bd6aeec9.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;Plugins&quot;&gt;Plugins&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;相关内容参考：&lt;a href=&quot;https://code.claude.com/docs/zh-CN/plugins&quot;&gt;https://code.claude.com/docs/zh-CN/plugins&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Plugin顾名思义，就是插件。它是commands、skill、subagent等的集合。Plugin面向的是一件事务的解决方案。&lt;/p&gt;
&lt;p&gt;本篇文章暂不赘述，后续我将创建一个从 语雀网站拉取博客，到自动发布微信公众号、个人网站、小红书的plugin，到时候再演示plugin的打包能力。&lt;/p&gt;
</content:encoded></item>
<item><title>2025 年度回顾：硕士毕业、工作与技术学习</title><link>https://wxxlamp.cn/2026/04/05/my-2025/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/04/05/my-2025/</guid><pubDate>Sun, 05 Apr 2026 09:13:00 GMT</pubDate><description>深入回顾2025年的个人成长轨迹，涵盖港硕毕业、工作发展、技术学习等方面，并对目标达成情况进行全面复盘，总结经验教训与未来规划。</description><content:encoded>&lt;p&gt;今年的农历春节过得比较晚，间接导致了2025年review姗姗来迟。&lt;/p&gt;
&lt;p&gt;此时坐在工位上，怀着很多种莫名的情绪，焦虑、懊悔、迷茫，结合着去年一整年的日记，努力的回想着去年的回忆，发现了太多决策失误的地方。&lt;/p&gt;
&lt;p&gt;2025年整体复盘，感觉略微有点不及预期：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;港硕虽然顺利毕业，但是没有把港硕发挥到最大价值，是绝对战略上的失误。不过好的地方就是视野确实有扩充，包含对&lt;a href=&quot;https://wxxlamp.cn/2025/12/24/ai-ml-getting-started/&quot;&gt;LLM基本原理&lt;/a&gt;、&lt;a href=&quot;https://wxxlamp.cn/2025/09/07/what-is-bitcion-and-blockchain/&quot;&gt;区块链&lt;/a&gt;等知识都有了进一步的了解（奈何没有转化出具体的结果）。&lt;/li&gt;
&lt;li&gt;工作上有了更深入的思考，对&lt;a href=&quot;https://wxxlamp.cn/2026/01/04/cross-border-ecommerce-transaction-flow/&quot;&gt;全球卖业务&lt;/a&gt;做的事情，自己和业务的联动，技术驱动、AI赋能，都有自己的认知。能够感觉到自己在工作上认知相对于&lt;a href=&quot;https://wxxlamp.cn/2025/02/16/ali-3-years-thought/&quot;&gt;3年前&lt;/a&gt;又有了进一步的提高。&lt;/li&gt;
&lt;li&gt;未来的规划上，对自己的能力有了清晰的认知。以前一直想要追风口，搞自媒体，视频、卖课啥都想弄。后来扑棱了好久，发现自己技能树也是真的没有在这个上面，所以现在也是彻底死心了。还是专心写文字，比较适合自己。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;接下来，按照老规矩，先细致地复盘下2025：&lt;/p&gt;
&lt;h1 id=&quot;2025年目标达成情况&quot;&gt;2025年目标达成情况&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;工作上，梳理跨境交易和资金履约整体的业务链路和知识，写1-2篇文档。&lt;/strong&gt; 本篇基本达成，花了大量时间写了交易的流程、资金的记账等业务逻辑，对自己将来应该是一笔不小的财富。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开发上，完善高并发场景热点系统的处理体系，写2-4篇文档&lt;/strong&gt;。这个目标达成20%的，在2025年底确实看了大量的相关文章，包含并发场景的一致性和性能问题。但是没有写文档落实下来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技术上，掌握ML-&amp;gt;DL-&amp;gt;NLP-&amp;gt;LLM的基础知识，使用Spring AI/LangChain搭建一个成熟的产品，写1-2篇文档&lt;/strong&gt;。目标达成40%，针对于LLM的基础知识产出了一篇文档，但是没有一个成熟的产品出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习上，拿到港硕的毕业证&lt;/strong&gt;。100%完成，10月15号，我毕业了。一年的港硕、一年的雅思备考和申请，就像梦一样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;职业上，争取更上一层楼，具体指标提现在绩效上。没有其他事情影响，工作上要all in了&lt;/strong&gt;。基本完成，S1 绩效不错，年度绩效应该不低于3.5+吧&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理财上，平均年化3%，更具体一点说，要通过把部分资金换成美元存定期来达成。&lt;/strong&gt; 对于未知的都有点担心，尤其是去年下半年人民币贬值，导致大量的本金都在银行里面，收益率也是惨不忍睹，2%都不到。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;心性上，聚焦1-2件重要的事情，停止发散。具体的规划可以立刻落地。&lt;/strong&gt; 这个flag现在看下来非常虚，没什么价值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作息上，晚上12点半睡，早上8点半起&lt;/strong&gt;。直接一个0%，去年的作息很差，甚至影响到了白天的精神。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运动上，本年度跑步700KM&lt;/strong&gt;。完成90%，去年跑650KM，这个有点失策了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;2025年复盘总结&quot;&gt;2025年复盘总结&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;2025年的主线任务只有两个，一个是顺利从UST毕业，另一个是工作中取得更好的成果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;25年是蛇年，元旦那天和鸡哥一起去香港的蛇山徒步，虽然不知道地点，但是玩的还算是挺开心的。不过每年的一月都是遗忘系数最大的一月，所以只记得一月份被各种琐事困扰，置办年货、办港卡，操心我弟的各种事情。不过月底回杭州出差，也算是在年前过了几天清闲日子。&lt;/p&gt;
&lt;p&gt;接着回家过年，2025年的春节很难说过得是一个轻松年，一回家各种事情就围着自己转，很多想在过年时间做的事情也没有完成，刚开始对于完不成规划还非常懊恼，但是后来竟然慢慢麻木了，所以对于完不成的规划，也默默允许了——这不是一个好结果。过年时在想，&lt;em&gt;&lt;u&gt;有时候经常把自己性格的缺点归结到家庭教育上，但是30岁以后它可能就不是一个好借口了，因为那时起我已经有一半时间不在家里，所有的结果都需要我自己买单&lt;/u&gt;&lt;/em&gt;。二月的中下旬非常难顶，主要是做巴坦PayPal的项目，从商户入驻、支付到结算、再到记账全链路，从小白到项目上线，确实给自己整的够呛。但是在这个项目中也确实收获了很多东西。&lt;/p&gt;
&lt;p&gt;三月份内心做了一次思考，希望将来的工作中心转为AI，同时自媒体运营也要跟上。具体的工作事项倒是没干啥。（站在26年review的角度对思考是给予100分肯定，但是3月之后的行动力实在是跟不上，因为这两件事除了微信公众号拿到将近1000的粉丝之外，AI相关的并没有实质上的进步）&lt;/p&gt;
&lt;p&gt;三四月不只是在工作，另一方面也是每周奔波在港深两地上课。大模型、NLP这两门课程确实给我搞的非常崩溃，作为一个连Python环境都不会配置的小白，突然就要用Python跑LLM的inference，导致我在前期写作业的时候，几乎有一半时间都是在补基础、配环境上。更痛苦的是，我跑代码的笔记本还是17年买的雷神，电脑的风扇和外面炽热的蝉鸣一起扰动，让我时不时地心烦意乱。不过好在可以白嫖公司的GPU资源，感谢阿里和百炼这俩平台，让我在周末得以拿到32核的GPU以完成作业。在这个过程中，我知道了怎么&lt;a href=&quot;https://github.com/wxxlamp/llm-fine-tuning&quot;&gt;SFT模型&lt;/a&gt;，以及&lt;a href=&quot;https://github.com/wxxlamp/nlp_individual_project&quot;&gt;模型的分类&lt;/a&gt;，收获确实挺大的。&lt;/p&gt;
&lt;p&gt;五月份是学校各个科目的project的ddl，几乎每天都铺在了作业上。第一次有可能顶不住的感觉，每天熬夜，根本无法把全部的精力都focus到工作上面。五月底所有作业都完成之后，就又重新陷入纠结中，自己的工作计划是什么？要不要趁着在香港读书的便利，留香港工作、还是去新加坡？&lt;/p&gt;
&lt;p&gt;六七八九月份又重新把重心转移到工作上，这个时候也发生了几件大事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;部门的协作机制发生了变化：组里引入了大量的OD员工，我一个人要在完成本职工作的前提下，再对接和培养2-3名OD员工。所以在这段时间内，每天都是9:50-10:30的高强度工作，感觉人都要虚脱了。后面觉得不能这么坐以待毙，就主动找老板明确了OD的业务人日，同时给自己的业务人日松绑，才有了一线喘息之机，也才能更好的培养OD承接需求。&lt;/li&gt;
&lt;li&gt;另外就是自己负责的业务领域也发生变化。因为组里有其他同事准备跑路，所以我又额外承接了资金的需求（支付和结算），对我来说是挑战，但是这个过程也确实因为新的业务场景了解到了更多的业务知识。这也是我S1拿到好绩效的重要原因之一。&lt;/li&gt;
&lt;li&gt;第三点就是带人。毕业四年后真正意义上的、官方口径的带实习生。小同学其实年龄只比我小一岁，但是浑身上下都透露着学生的呆呆的气质。对他的指导也自认为比较用心，也是成功在9月份通过转正答辩。&lt;/li&gt;
&lt;li&gt;白嫖公司经费去韩国出差，这其实还是自己第一次出国。深圳真是个神奇的地方，自从24年来了深圳之后，去了香港、又去了韩国，了解到了不同的生活方式和工作风格。感觉世界充满了无限的可能。&lt;/li&gt;
&lt;li&gt;第一次组织团队自驾团建。和实习生、外包、团队其他同学一起去了广东318线路自驾，一次非常amazing的旅途。&lt;/li&gt;
&lt;li&gt;写了几篇爆款文章：&lt;a href=&quot;https://wxxlamp.cn/2025/08/15/how-to-use-mac/&quot;&gt;程序员如何配置MBP&lt;/a&gt;，历史第一次让微信公众号浏览量3w+，也算是一个小的突破。后续趁热打铁，又写了区块链和数字人民币相关的文章，数据都比较好。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;十月份是最终毕业的时间，终于给自己一年的港硕生涯画上了句号，甚至还拿到了学校的Top Student Reward奖学金。同时工作上开始打S1的绩效，拿到了一个还可以的绩效。本来想一鼓作气继续往前冲，但是月底得知业务给另一个老板了，这个老板还带了几个嫡系过来，感觉局势瞬间复杂了起来。&lt;/p&gt;
&lt;p&gt;剩下的时间重新开始准备11月初的软考，软考的结果令我大吃一惊，综合知识和案例分析通过，本来以为胜券在握的论文部分，竟然挂了，令我难过了好一阵，一度觉得付出没有收获，甚至产生了自己即使再努力也拿不到的错觉，还是挺打击自己自信的。难道，人生都有经历这么一个过程？这个时候我才想起来王小波的一句话，想起来20岁过生日的自己。那时我的想变成天上的云，现在，好像被 慢慢 锤扁了。&lt;/p&gt;
&lt;p&gt;本来应该好好学习AI，准备各种agent相关的开发的知识付费课程。但是因为自己实在是太好奇了，所以11月底和12月初的时候，做了科大区块链老师的TA，老师是一个印度人，口音比较重，沟通起来还是比较麻烦的。另外因为这次经历，又重新学习了&lt;a href=&quot;https://wxxlamp.cn/2025/09/07/what-is-bitcion-and-blockchain/&quot;&gt;区块链、ETH&lt;/a&gt;相关的内容，整体来说还是有收获的。不过现在看来，路径出现了偏差，那个时候就应该all in Agent，最起码完成一下&lt;a href=&quot;https://wxxlamp.cn/2025/03/07/my-2024/&quot;&gt;24年的目标&lt;/a&gt;才行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;做个简单的总结，25年各种分支线路太多了，偏离了24年的目标，严重偏离对AI Agent的感知和掌握（LLM基础了解的还是可以的），导致26年的一系列决策都受到了影响。&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;2026年Flag&quot;&gt;2026年Flag&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;工作上，找到新的方向并成功landing，将AI coding和working彻底的代入到工作中。&lt;/li&gt;
&lt;li&gt;技术上，发现系统潜在的事务一致性问题，并解决。&lt;/li&gt;
&lt;li&gt;AI上，掌握一个agent框架的原理（Spring AI、LangChain、Eino等），了解Coding Agent的工作原理&lt;/li&gt;
&lt;li&gt;写作上，强迫协作，每月两篇微信公众号博客【最重要的】&lt;/li&gt;
&lt;li&gt;学习上，拿到软考的证书&lt;/li&gt;
&lt;li&gt;理财上，配置10%的美股资产，收益率争取到3%&lt;/li&gt;
&lt;li&gt;运动上，700KM&lt;/li&gt;
&lt;li&gt;健康上，做一次肠胃镜、养成12点半到8点半的作息习惯。同时停止久坐，把下腹部的赘肉减一减&lt;/li&gt;
&lt;li&gt;专一上，超过20个工作日，手机使用时长不超过2小时。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item>
<item><title>大模型与 Agent 如何降低技术使用门槛</title><link>https://wxxlamp.cn/2026/01/10/llm-agent-tech-equality/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/01/10/llm-agent-tech-equality/</guid><pubDate>Sat, 10 Jan 2026 07:42:00 GMT</pubDate><description>通过一次Git分支污染事件，探讨大模型和Agent如何实现技术平权，让非专业开发者也能高效解决复杂技术问题。</description><content:encoded>&lt;p&gt;我对git确实不算了解。&lt;/p&gt;
&lt;p&gt;之前校招面试的时候虽然把面试官哄成胎盘，&lt;code&gt;merge&lt;/code&gt;、&lt;code&gt;revert&lt;/code&gt;指令在电话中信手拈来。但是不得不说，其实没怎么用过。&lt;/p&gt;
&lt;p&gt;工作四年多，只会&lt;code&gt;pull&lt;/code&gt;、&lt;code&gt;add&lt;/code&gt;、&lt;code&gt;commit&lt;/code&gt;、&lt;code&gt;checkout&lt;/code&gt;、&lt;code&gt;push&lt;/code&gt;五剑客，复杂的git指令和协作流程日常也没怎么用到，所以工作之后也就没有动力仔细了解过其他复杂的git指令。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/d951595f_f58e3da3.png&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;但是常在路边走，哪有不湿鞋。&lt;/p&gt;
&lt;p&gt;昨天晚上10点左右，组里一个同事小A像往常一样&lt;code&gt;commit&lt;/code&gt;、&lt;code&gt;push&lt;/code&gt;分支需求的代码。但是&lt;code&gt;push&lt;/code&gt;到测试分支之后，意料之中的发现了冲突——接近年底，不下五个需求分支被合并到了测试分支等待部署到测试环境测试。&lt;/p&gt;
&lt;p&gt;这种情况也很常见，小A把测试分支从远程拉了下来，本意是将自己的需求分支合并到测试分支中，但是没想到，一手抖——将测试分支的几百个&lt;code&gt;commit&lt;/code&gt;全部合并到了自己的需求分支上。&lt;/p&gt;
&lt;p&gt;此时小A还没有意识到事情的严重性，或者说他还没有意识到问题，继续在本地的需求分支上提交代码。&lt;/p&gt;
&lt;p&gt;当他终于本地提交了几轮，确定本地代码没问题后，准备&lt;code&gt;push&lt;/code&gt;到远程测试分支的时候，才突然发现，自己的分支已经被测试分支污染了——而他第二天就要提测和发布了，怎么可能将别人的测试分支也发布上线呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/65798b24_d3d68755.png&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;小A有点慌了。&lt;/p&gt;
&lt;p&gt;但是小A和我一样，也对git不算太过了解，折腾了一小时，还是没有把本地的需求分支的污染分支删掉。&lt;/p&gt;
&lt;p&gt;没办法，小A基于原来的分支和以前的提交数据，&lt;code&gt;cherry-pick&lt;/code&gt;了一个新的分支。弄完之后，一看表已经凌晨了，拖着疲惫的身体，踩着月色，小A打了个车，回家了。&lt;/p&gt;
&lt;p&gt;事情到此，没有出什么大影响，但是我想，一定有方法将原来分支的垃圾&lt;code&gt;commit&lt;/code&gt;数据删除，所以第二天一早我打开了GPT。&lt;/p&gt;
&lt;p&gt;正当我敲入问题“A分支误&lt;code&gt;merge&lt;/code&gt;了B分支，如何将B分支的多个&lt;code&gt;commit&lt;/code&gt;数据删除”，准备按下回车键时，突然想到：“能不能直接让claude帮我操作？”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/26bf29ab_3e790e50.png&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;所以就发生了我和claude的如下对话：&lt;/p&gt;
&lt;p&gt;我：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;我的git分支不小心merge了其他分支，请帮我回退到commiter=小A，最新一次提交的代码的那次提交&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CLAUDE：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;收到。我需要先查找“小A”的最新提交哈希，并检查当前工作区的状态。 &lt;/font&gt;&lt;br&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;检测到目标提交为 &lt;/font&gt;&lt;font style=&quot;color:rgb(38, 38, 38);background-color:rgba(0, 0, 0, 0.06);&quot;&gt;commit_hash_A&lt;/font&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;。同时发现当前正处于一个交互式 rebase 操作中，且存在合并冲突。为了安全回退，我需要先中止当前的 rebase，再执行硬重置。&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;帮我回退到这里：a3a8f43dfa8eac801e804f76cf2c36450c717479&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CLAUDE：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;没问题，直接将 HEAD 指针重置到该节点。&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;我：&lt;/font&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;能帮我把本地分支强制push到远程仓库吗&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CLAUDE：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;好的，确认当前分支为 &lt;/font&gt;&lt;font style=&quot;color:rgb(38, 38, 38);background-color:rgba(0, 0, 0, 0.06);&quot;&gt;feature_1&lt;/font&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;。由于刚才进行了回退操作，本地落后于远程，需要执行强制推送。&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;我：&lt;/font&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;我发现远程仓库还有之前merge的提交，和我本地的不一样，如何消除？&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;CLAUDE：&lt;/font&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;让我对比一下本地 &lt;/font&gt;&lt;font style=&quot;color:rgb(38, 38, 38);background-color:rgba(0, 0, 0, 0.06);&quot;&gt;feature_1&lt;/font&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt; 和远程分支的提交历史差异。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;分析发现：虽然本地指针是对的，但远程历史中包含了大量非线性的 Merge Commit（由构建系统自动产生）。我们需要清理时间线。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;请确认你的清洗策略：&lt;/font&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;彻底丢弃&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;目标节点之后的所有提交？&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;还是&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;保留&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;目标节点后的正常代码提交，仅剔除那些 Merge 节点？&lt;/font&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;我：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;想要保留 a3a8f43dfa 之后的正常提交，只删除那些 merge 提交（非“小A”的提交）&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;CLAUDE：&lt;/font&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;明白了。这是一个复杂的“变基清洗”任务。 &lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;策略&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;：基于干净的节点创建一个临时分支，从旧分支中“捡出（Cherry-pick）”属于小A的有效提交，拼接到新分支上，最后替换原分支。&lt;/font&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;创建备份与临时分支&lt;/font&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;筛选并迁移小A的提交&lt;/font&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;替换主分支并推送&lt;/font&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/33d938e1_d745243d.png&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;从给claude提问，到claude完全完成，总共时间不超过十分钟，而且又快又好，那一瞬间，真的让我觉得实现了技术平权。&lt;/p&gt;
&lt;p&gt;人们对大模型的应用，不应当只停留在了对话的阶段，通过MCP、RAG各种能力集成的Agent，在prompt得当的时候，真的可以帮我们完成很多事情。&lt;/p&gt;
&lt;p&gt;在公司内部，我们也基于大模型和Coder Agent完成了自主的生码能力，同时，很多团队也在积极探索基于大模型和agent的工单自主排查、自主联调等能力。&lt;/p&gt;
&lt;p&gt;对于个人用户来讲，也应该利用agent来完成很多琐事的自动处理。在以后，我们的工作范式甚至会发生变化，学会如何和agent说话，如何监控agent可能是一个重要的命题。&lt;/p&gt;
&lt;p&gt;在未来，大模型的基础能力很重要，也就是我们常说的pre-training。有的人会说post-training也很重要，这个我不否认，&lt;strong&gt;但是消费市场，agent的能力才是很多人工智能产品决胜的关键&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我想这也是为什么Meta花费十亿美元收购一家不做model training、只做agent公司的原因，同时，这也是为什么阿里Qwen明明已经全球开源第一了，却也还是干不够豆包的原因之一。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/5599e669_fb6d2a35.png&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;最后回到今天的话题，如何快速的把git merge的分支删除呢？请看下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/94da7da8_f317e49c.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：本文图片采用Gemini Nano Banana Pro生成&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item>
<item><title>跨境电商交易全流程：信息流、资金流与履约</title><link>https://wxxlamp.cn/2026/01/04/cross-border-ecommerce-transaction-flow/</link><guid isPermaLink="true">https://wxxlamp.cn/2026/01/04/cross-border-ecommerce-transaction-flow/</guid><pubDate>Sun, 04 Jan 2026 06:16:00 GMT</pubDate><description>深入解析跨境电商平台交易履约全流程，从买卖家视角剖析信息流、资金流与物流的协同机制，揭秘平台盈利模式与资金结算体系。</description><content:encoded>&lt;p&gt;作为在线冲浪选手，日常买买买都需要用到某宝、某东和某多多等在线购物平台。&lt;/p&gt;
&lt;p&gt;从24年8月转岗后，笔者便一直负责类似于某宝某东的在线购物类业务，不过有两个不同点。第一个不同点是某宝某东是面向普通消费者的2C购物平台，而笔者写的代码则是作用于2B贸易类的购物平台。第二个不同点是某宝某东面向的是国内的商家和消费者，而笔者日常面对的则是国外的商家和国外的消费者。&lt;/p&gt;
&lt;p&gt;在过去一年里，笔者开发的代码包含了商户的入驻、交易、订单、保障、支付、结算、流动性、账务、网关等各个领域。作为2026年第一篇文章，便是对笔者过去一年左右的工作进行一次总结。同时也主要科普下跨境的交易履约全流程。&lt;/p&gt;
&lt;h1 id=&quot;买卖家在电商平台要干啥&quot;&gt;买卖家在电商平台要干啥&lt;/h1&gt;&lt;p&gt;电商平台平台的在线交易的本质其实就是负责信息流、资金流和物流的流转——1）交易承载买卖家约定的信息流 2）资金负责将买家的货款合法合规的调拨到商家的卡上 3）物流负责将商家的货物安全的运送到买家指定的地点。&lt;/p&gt;
&lt;p&gt;下面从买卖家视角和平台视角做一个简要的总结。&lt;/p&gt;
&lt;h2 id=&quot;买卖家视角——交易的生命周期&quot;&gt;买卖家视角——交易的生命周期&lt;/h2&gt;&lt;p&gt;从买卖家视角来看，全链路视图如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/5606db74_ab1a042e.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上面的图我们就可以看出来，买家付款完成后，中间有一个“&lt;strong&gt;放款&lt;/strong&gt;”的动作。这说明货款是不可能直接直接到商家账户的，因为我们没办法保证商家后续会正常履约。所以如果你有留意的话，当你在淘宝平台确认收货的时候，会让你输入支付密码（或者支付宝人脸认证），这就是因为对于淘宝来说，只有在确认收获的时候，你的钱才真正支付给商家。&lt;/p&gt;
&lt;p&gt;从商家和买家视角来看，核心有如下几个行动点：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/59b28626_e8a5f5e5.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;平台视角——平台在交易中的角色&quot;&gt;平台视角——平台在交易中的角色&lt;/h2&gt;&lt;p&gt;从平台视角来看，我们要核心串联起在交易和资金环节，不同业务领域的流程。一个简化的流程图如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/3ebf4508_2a52ab59.png&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;电商的参与主体和盈利模式&quot;&gt;电商的参与主体和盈利模式&lt;/h1&gt;&lt;h2 id=&quot;参与主体——交易背后的很多双眼睛&quot;&gt;参与主体——交易背后的很多双眼睛&lt;/h2&gt;&lt;p&gt;对于普通的消费者来说，这个只是买卖家在平台上完成了一笔交易，但是从平台的角度出发，交易的参与方不仅涉及平台和买卖家，还会涉及到中间的各种资金渠道。&lt;/p&gt;
&lt;p&gt;以一个基础的跨境在线交易平台举例，如果平台没有资质感知买家的资金，除了买卖家、平台之外，还需要有一个额外的资金中心。协议如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/1afff81c_7693194e.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当平台具备持有资金的拍照后，为了增强对资金的把控能力，往往让收单机构把钱结算给平台，然后平台再把钱通过出金机构结算给全球的卖家。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/367b905e_407deea6.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;参与的每一个主体之间都应该有一个协议来约束。&lt;/p&gt;
&lt;p&gt;买家需要和平台签署协议，采购商品并支付货款；商家需要和平台签署协议，交易商品并提取资金；Adyen作为收单渠道也要和平台签署对应的收单协议；Payout作为出金渠道要和平台、商家双方签订相应的支付服务协议。如果有汇兑机构（用于将外币汇兑成USD），汇兑机构也需要和平台签署协议。&lt;/p&gt;
&lt;h2 id=&quot;盈利模式——平台是如何赚钱的&quot;&gt;盈利模式——平台是如何赚钱的&lt;/h2&gt;&lt;p&gt;从消费者的视角来看，在线交易可以从每笔交易中对商家抽取交易服务费。但是在跨境场景，平台的盈利方式多种多样：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;交易维度&lt;ol&gt;
&lt;li&gt;交易服务费：每笔会收取商家3%左右的技术服务费。这笔钱全归平台所有，对应的，平台需要给商家开具发票。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;支付维度&lt;ol&gt;
&lt;li&gt;支付手续费：每次支付会收取支付方（买家）3%（依据支付工具而定）的手续费。和境内不同，境外支付工具基本每次支付都会收取支付方一定比例的手续费（如信用卡基本就是2%），平台会按照规则固定和支付机构进行分佣。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;提现维度&lt;ol&gt;
&lt;li&gt;提现手续费：商家每次将平台余额户的资金提现到银行卡都会被收取一定比例或者金额的手续费。一般由平台代收，然后平台和出金机构进行分成。这个不分境内境外，只要商户提现都会有手续费，譬如支付宝和微信的提现手续费都是0.1%&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;增值服务&lt;ol&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;/ol&gt;
&lt;/li&gt;
&lt;li&gt;商家维度&lt;ol&gt;
&lt;li&gt;商家入驻Alibaba国际站成为会员，需要缴纳一定金额的会员费&lt;/li&gt;
&lt;li&gt;商家可以购买各种各样的服务来提高店铺和商家的曝光度，进而提高转化率。这个基本是所有在线平台的盈利核心，如淘宝的阿里妈妈、抖音的巨量引擎等等&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;物流维度&lt;ol&gt;
&lt;li&gt;商家发货会给平台缴纳物流费，费用与物流公司分成&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/88f308d8_55e4f92c.png&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;跨境资金清结算&quot;&gt;跨境资金清结算&lt;/h1&gt;&lt;p&gt;交易代表信息流，另外一个重要部分就是资金流。所谓资金流，最底层的就是资金机构之间的资金调拨，伴随而生的是账户之间的记贷关系。基于资金调拨和记账，上游衍生出了商户入驻、支付、结算、提现、调拨、外汇等业务场景。&lt;/p&gt;
&lt;h2 id=&quot;资金链路——你的钱是怎么到商家口袋的&quot;&gt;资金链路——你的钱是怎么到商家口袋的&lt;/h2&gt;&lt;p&gt;对于一个跨境平台来说，因为建设初期，为求快速建站，收单平台会提供USD结算能力，所以不需要引入外汇机构。注意，因为我们是跨境平台，所以在当前美元霸权的体系下，我们默认的首选结算币种都是USD。&lt;/p&gt;
&lt;p&gt;资金流图示如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/129fbdba_99a72499.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;**第一阶段：**全球的买家在下单之后，进入支付流程。通过CREDIT CARD、GOOGLE PAY、APPLY PAY、PAYPAL和其他本地化支付方式把钱打给收单机构（譬如说Adyen）。然后Adyen内部会进行币种的汇兑，之后等待T+1把钱结算给平台主体。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; **第二阶段：**平台会通过T+1调拨的方式将收单机构结算给平台的钱，再按照商家提现的金额，定额调拨到平台在出金机构开立的账户中。此时商家可以直接将平台余额户的钱提现到商n家自己的账户中。要说明的是，为了保障商家能够尽快提现，项目采用垫付的方式——在项目运行前，会通过财资户往出金机构提前垫资。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当平台成熟发展之后，为了引入更多的支付工具，是一定会增加多个收单机构的，当收单机构不支持USD结算时，平台就需要引入汇兑机构，由汇兑机构将其他杂币转成USD结算给平台。此时的资金流如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.nlark.com/yuque/0/2026/png/719664/1767518918849-342aa652-3e86-4fbb-ac9c-4fd212fede98.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;由上图可以看出，此时平台需要感知用户的支付币种，所以平台需要自己完成LCY到USD的锁汇和汇兑。&lt;/p&gt;
&lt;h2 id=&quot;账务能力——平台是如何记钱的&quot;&gt;账务能力——平台是如何记钱的&lt;/h2&gt;&lt;p&gt;既然钱从买家到商家的手中需要经过N次流转和调拨，那么如何保证钱记的一定是对的呢？这就需要了解到平台的账务能力，即平台的记钱方式。平台会在内部建立N个虚拟户头来描述钱在不同账户的流转。在互联网公司中，一般采用复式记账法来几张，感兴趣的朋友可以了解下。本文简单列举下几种经典的账户&lt;/p&gt;
&lt;ol&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;li&gt;&lt;strong&gt;收入户&lt;/strong&gt;：系统会把平台应收部分（交易服务费、手续费等）转到收入户&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;税金户&lt;/strong&gt;：系统会把商家代扣代缴的税费（如EPR、GST等）转到税金户，定时申报到对应机构&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;li&gt;&lt;strong&gt;退款过渡户&lt;/strong&gt;：买家发起退款后，我们需要通过资金准备的方式将钱汇集到退款过渡户中，打给支付渠道&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里捞了一个经典的流程图（包含正逆向的），可以详细了解下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/b01fb88d_b3430345.png&quot;&gt;&lt;/p&gt;
</content:encoded></item>
<item><title>面向后端开发者的机器学习入门：神经网络与模型训练</title><link>https://wxxlamp.cn/2025/12/24/ai-ml-getting-started/</link><guid isPermaLink="true">https://wxxlamp.cn/2025/12/24/ai-ml-getting-started/</guid><pubDate>Wed, 24 Dec 2025 13:50:00 GMT</pubDate><description>从后端开发视角入门机器学习，厘清AI、ML、DL、LLM的关系，详解神经网络、CNN、RNN原理及Embedding、前反向传播等核心概念，附Python代码示例。</description><content:encoded>&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/0faf3c20_img1.png&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;&lt;p&gt;从2017年Transformer架构横空出世，再到2022年GPT的出现，人们愈发发现大模型的重要性，每一年仿佛都是LLM元年。在逐步步入AI时代的过程中，我感觉到十分的焦虑，担心被大模型替代，也感觉到兴奋，希望能通过LM做更多的事情。&lt;/p&gt;
&lt;p&gt;但是我能够通过LLM做什么呢？我又需要了解什么呢？&lt;/p&gt;
&lt;p&gt;在大模型时代以前，后端程序员一般需要掌握开发语言（Java、Go、Js等）、Mysql、Redis的架构知识就可以上手开发软件了。但是在AI时代，或者说是大模型时代，显然只掌握一般的开发语言已经不够看了。&lt;/p&gt;
&lt;p&gt;LLM工程一句话说完就是使用各种PROMPT调用LLM，与此而来的有MCP、RAG等各种工程能力。如果想要快速的将LM能力嵌入到业务场景中，本质上和上古时期的CURD程序员一样，直接调用大模型厂商的开放接口即可。&lt;/p&gt;
&lt;p&gt;但是只调用模型的API，如水中观月、镜里看花，始终是看不透本质和系统全貌的。私以为，对于后端程序员来说，要想融入AI时代，首先要做的不是调用LLM相关的API，而是要了解传统人工智能和机器学习相关的概念和原理，比如ML和DL到底是啥关系？CNN、RNN、Transformer这些架构是怎么演化的？。如果不了解这些基本的知识，就如同开发时代不了解操作系统、计算机网络一样——可以完成基本的编程，但是无法更进一步地窥到计算机世界的奇妙。&lt;/p&gt;
&lt;p&gt;所以我花了一点时间，借助一些常见的问题，梳理了一下我对于人工智能和机器学习的理解。&lt;/p&gt;
&lt;h1 id=&quot;你的AI我的AI好像不一样&quot;&gt;你的AI我的AI好像不一样&lt;/h1&gt;&lt;p&gt;&lt;font style=&quot;color:rgb(31, 31, 31);&quot;&gt;这几年随着大模型的兴起，一切皆AI。狭义来说，AI就是人工智能，实现AI的核心人群被称为”算法工程师”（常常被称作调参侠）。大模型时代，很多人会直接用大模型代指AI，譬如罗永浩一贯的观点就是23年才迎来真正的AI时代。那么人工智能、机器学习、深度学习、大模型，它们之间的关系和区别在哪里呢？&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(31, 31, 31);&quot;&gt;根据李宏毅的理论，我们常认为人工智能（Artificial Intelligence, AI）是最终要达到的目标，而主要手段则是机器学习（Machine Learning, ML）。计算机视觉（Computer Vision, CV）、自然语言处理（Natural Language Processing, NLP）等则是一些具体的应用。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(31, 31, 31);&quot;&gt;从一个外行的角度出发，ML是一个泛称，其核心逻辑是”通过数据训练模型，让计算机自主学习规律以完成特定任务”。在ML的技术分支中，深度学习（Deep Learning, DL）是当前最核心、最主流的方向。深度学习以”深度神经网络”为核心架构，拥有多层多个神经元进行学习，神经网络从基础到复杂，分为基本的前馈神经网络（Feedforward Neural Network, FNN）、处理序列数据的循环神经网络（Recurrent Neural Network, RNN）、处理空间数据的卷积神经网络（Convolutional Neural Network, CNN）等。Google提出的Transformer架构则重构了原来RNN和CNN序列式处理的原则，采用Self Attention机制，解决了序列依赖的问题，使得模型不仅可以感知token之间的关系，也具备并行训练的能力。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(31, 31, 31);&quot;&gt;至于大模型（Large Models, LM），它的大不仅是指的训练模型的参数大，也是指的训练依赖的数据多。正是借助于Transformer的能力，才使得模型变”大”成为了可能。大模型最开始的一个分支是大语言模型（Large Language Models, LLM），起源于NLP领域，适用于文生文等各种NLP的传统任务。不过随着LM多模态的发展，目前LM也不仅局限于NLP领域，而是为了实现AI这个目标触及到了应用的各个方面。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/7b498439_img2.png&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;机器学习能干什么事&quot;&gt;机器学习能干什么事&lt;/h1&gt;&lt;p&gt;虽然从应用的领域来讲，基于ML的LLM具备文生文、文生图、图生文等能力。但是追其根本，所有的应用层任务都会回归到”回归（Regression）”和”分类（classification）”两种任务上来。&lt;/p&gt;
&lt;p&gt;所谓回归，类似于y=kx+b，根据输入的不同，会输出不同的值。回归任务常用于预测（如天气预报、房价和股票走势等）。 而分类则是在回归的基础上，通过激活或者分类函数，将结果分成某几类（譬如正数和负数两类）。分类任务常用于打标签等。&lt;/p&gt;
&lt;p&gt;我们常说的搜推广中的推荐，就属于分类任务。而众所周知，大模型虽然会根据预测的概率生成文字，但其本质是在词表中（譬如50000个单词）预测下一个词（Token），所以大模型属于分类任务。&lt;/p&gt;
&lt;h1 id=&quot;模型到底是个什么东东&quot;&gt;模型到底是个什么东东&lt;/h1&gt;&lt;p&gt;随着大模型的普及，几乎每个人都听说过”模型”两个字。那么模型究竟是什么。&lt;/p&gt;
&lt;p&gt;从宏观上理解，模型是一个黑盒。通过为模型提供输入，模型内部通过计算后，最后输出得到最终的输出结果。&lt;/p&gt;
&lt;p&gt;从huggingface的开源模型库中可以看到，模型长这样：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/a97f77f1_img3.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;模型内部是一系列的参数（weight），模型思考的过程，本质上就是结合输入的token进行一系列的向量运算（&lt;strong&gt;主要是矩阵乘法&lt;/strong&gt;）。在大模型时代，我们可以更加聚焦一点，认为模型内部就是由一堆神经元组成的神经网络。那么，神经元又是个什么东东？&lt;/p&gt;
&lt;h1 id=&quot;神经元和神经网络&quot;&gt;神经元和神经网络&lt;/h1&gt;&lt;h2 id=&quot;神经元&quot;&gt;神经元&lt;/h2&gt;&lt;p&gt;神经元简单理解就是一个计算单元。假定输入为x，神经元可以是wx+b。经过神经元计算的输出就可以是y。只不过x不是高中数学中学到的一个变量，而是和输入相关的n个变量组成的一个一维向量：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(y = \sigma(w_1 x_1 + w_2 x_2 + b)\)&lt;/span&gt;，即：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(y = \sigma([w_1, w_2] \cdot \begin{bmatrix} x_1 \\ x_2 \end{bmatrix} + b)\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;在上面的表达式中，&lt;span class=&quot;math-inline&quot;&gt;\(x_1\)&lt;/span&gt;和&lt;span class=&quot;math-inline&quot;&gt;\(x_2\)&lt;/span&gt;是指输入的特征，譬如针对一个天气预报预测模型，&lt;span class=&quot;math-inline&quot;&gt;\(x_1\)&lt;/span&gt;可能是湿度，&lt;span class=&quot;math-inline&quot;&gt;\(x_2\)&lt;/span&gt;可能是温度。&lt;span class=&quot;math-inline&quot;&gt;\(w_1\)&lt;/span&gt;和&lt;span class=&quot;math-inline&quot;&gt;\(w_2\)&lt;/span&gt;指的是变量的权重，也是我们平常说的”参数”，所谓的训练，就是不断调整&lt;span class=&quot;math-inline&quot;&gt;\(w\)&lt;/span&gt;（和其他参数）的值，使得输出&lt;span class=&quot;math-inline&quot;&gt;\(y\)&lt;/span&gt;更贴近于真实值（loss更低）。&lt;span class=&quot;math-inline&quot;&gt;\(b\)&lt;/span&gt;指的是偏置（通俗一点说就是截距），可以对计算数据进行增加或者减少。括号外面的符号是sigma (&lt;span class=&quot;math-inline&quot;&gt;\(\sigma\)&lt;/span&gt;)，它用来表示激活函数，常见的激活函数有 Sigmoid（&lt;span class=&quot;math-inline&quot;&gt;\(\sigma(x) \in (0,1)\)&lt;/span&gt;）、ReLU（&lt;span class=&quot;math-inline&quot;&gt;\(relu(x) = max(0,x)\)&lt;/span&gt;）等。激活函数用来控制输出，使得结果变得不再线性。&lt;/p&gt;
&lt;p&gt;形象化的神经元如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/f8365a77_img4.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;简单神经元使用python代码表达如下：&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;simple_neuron_torch&lt;/span&gt;(&lt;span class=&quot;params&quot;&gt;x, w, b, activation=torch.sigmoid&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    z = torch.dot(x, w) + b  &lt;span class=&quot;comment&quot;&gt;# 点积是标量&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; activation(z) &lt;span class=&quot;comment&quot;&gt;# 通过sigmoid激活&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;x = torch.tensor([&lt;span class=&quot;number&quot;&gt;28.0&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;75.0&lt;/span&gt;])  &lt;span class=&quot;comment&quot;&gt;# 今天：温度28°C，湿度75%&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;w = torch.tensor([&lt;span class=&quot;number&quot;&gt;0.5&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;0.5&lt;/span&gt;])   &lt;span class=&quot;comment&quot;&gt;# 权重：高温抑制下雨(-0.5)，高湿度促进下雨(+0.8)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;b = torch.tensor(-&lt;span class=&quot;number&quot;&gt;15.0&lt;/span&gt;)         &lt;span class=&quot;comment&quot;&gt;# 偏置：基准阈值&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;output = simple_neuron_torch(x, w, b)  &lt;span class=&quot;comment&quot;&gt;# 得到最后的下雨概率（0.9999）&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;假设训练出来的参数为 &lt;span class=&quot;math-inline&quot;&gt;\(w_1=0.5, w_2=0.5, b=-40\)&lt;/span&gt;，即：&lt;span class=&quot;math-inline&quot;&gt;\(y = \sigma(0.5 x_1 + 0.5 x_2 - 40)\)&lt;/span&gt;。我们会得到预测趋势如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/b849170f_img5.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上的图中，我们会得到结论：温度越高、湿度越大、越容易下雨。下面我们来分析：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;情况 A (桑拿天): &lt;span class=&quot;math-inline&quot;&gt;\(0.9 \times 0.5 + 0.9 \times 0.5 = 0.9\)&lt;/span&gt; (预测下雨，正确 ✅)&lt;/li&gt;
&lt;li&gt;情况 B (沙漠): &lt;span class=&quot;math-inline&quot;&gt;\(0.9 \times 0.5 + 0.0 \times 0.5 = 0.45\)&lt;/span&gt; (预测不下雨，正确 ✅)&lt;/li&gt;
&lt;li&gt;情况 C (极端高温干燥): 假设温度超级高 &lt;span class=&quot;math-inline&quot;&gt;\(x_1=2.0\)&lt;/span&gt;：&lt;ul&gt;
&lt;li&gt;&lt;span class=&quot;math-inline&quot;&gt;\(2.0 \times 0.5 + 0.0 \times 0.5 = 1.0\)&lt;/span&gt; (预测下雨，错误 ❌)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们发现，即使训练出来的w和b是最优的，得到的一定是一个线性的结果。但是，天气预报不是一件线性预测的事情。所以我们就需要尝试其他工具来解决问题。&lt;/p&gt;
&lt;h2 id=&quot;全连接神经网络&quot;&gt;全连接神经网络&lt;/h2&gt;&lt;p&gt;可以发现，简单的神经元只能做线性回归。但是世界其实是很复杂的，所以我们就需要尝试将多个神经元链接在一起，执行更复杂的任务。&lt;/p&gt;
&lt;p&gt;所以从天气预报的例子我们可以得知，天气预报不是一件线性预测的事情。预测天气的特征之间是有关系的，而我们无法通过单一的神经元获取到这些关系。从工程的角度来讲，遇事不决就加一个中间层。既然温度和湿度之间是有关系的，我们就把温度和湿度进行关联，形成多个中间特征，再由这些中间特征产生最后的结果。如下图表示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/e0204d08_img6.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;转换成简单的数学公式，如下：&lt;/p&gt;
&lt;div class=&quot;math-display&quot;&gt;\[h_1 = \sigma(w_{11} x_1 + w_{12} x_2 + b_1) \\
h_2 = \sigma(w_{21} x_1 + w_{22} x_2 + b_2) \\
h_3 = \sigma(w_{31} x_1 + w_{32} x_2 + b_3) \\
y_{final} = \sigma(v_1 h_1 + v_{2} h_2 + v_3 h_3 + b_4)\]&lt;/div&gt;&lt;p&gt;因为上面的公式可以尽可能的将&lt;span class=&quot;math-inline&quot;&gt;\(x_1, x_2\)&lt;/span&gt;全部关联起来，所以我们把上面的网络称为全连接神经网络（Fully connected network），它是目前深度学习的基础。如果把上面的FCN带入到我们湿度和温度的例子中，我们令&lt;span class=&quot;math-inline&quot;&gt;\(h_1\)&lt;/span&gt;负责桑拿天属性、&lt;span class=&quot;math-inline&quot;&gt;\(h_2\)&lt;/span&gt;负责湿冷天属性、&lt;span class=&quot;math-inline&quot;&gt;\(h_3\)&lt;/span&gt;负责干热天属性，假定训练结果如下：&lt;/p&gt;
&lt;div class=&quot;math-display&quot;&gt;\[h_1 = \sigma(x_1 + x_2 -1.2) \\
h_2 = \sigma(-x_1 + x_2 -0.5) \\
h_3 = \sigma(x_1 - x_2 - 1.0) \\
y_{final} = \sigma(2 h_1 + 0.5 h_2 - h_3 -1)\]&lt;/div&gt;&lt;p&gt;我们可以得到如下的预测图：可以发现经过中间层之后，就不会再出现”高温干燥”也会下雨的情况了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/53f822d5_img7.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;回到数学公式中来看，如果有多个特征、多层神经元，上面的表达未免太过复杂，所以借助矩阵相乘的空间变换特性（旋转、缩放、扭曲），我们可以把上述的中间隐藏层做如下转换：&lt;/p&gt;
&lt;div class=&quot;math-display&quot;&gt;\[h = \begin{bmatrix} h_1 \\ h_2 \\ h_3 \end{bmatrix} = \begin{bmatrix}
\sigma(w_{1,1}x_1 + w_{1,2}x_2 + b_1) \\
\sigma(w_{2,1}x_1 + w_{2,2}x_2 + b_2) \\
\sigma(w_{3,1}x_1 + w_{3,2}x_2 + b_3)
\end{bmatrix} = \sigma(\begin{bmatrix}
w_{1,1} &amp;amp; w_{1,2} \\
w_{2,1} &amp;amp; w_{2,2} \\
w_{3,1} &amp;amp; w_{3,2}
\end{bmatrix} \cdot \begin{bmatrix} x_1 \\ x_2 \end{bmatrix} + \begin{bmatrix} b_1 \\ b_2 \\ b_3 \end{bmatrix}) = \sigma(Wx + b)\]&lt;/div&gt;&lt;p&gt;，&lt;/p&gt;
&lt;p&gt;所以可以得到最终的输出为：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(y_{final} = \sigma( \underbrace{\begin{bmatrix} v_1 &amp;amp; v_2 &amp;amp; v_3 \end{bmatrix}}_{W_2} \cdot \underbrace{\begin{bmatrix} h_1 \\ h_2 \\ h_3 \end{bmatrix}}_{h} + b_4 )\)&lt;/span&gt; 。&lt;/p&gt;
&lt;p&gt;但是，在用代码训练的时候，我们要尽可能的并行做训练，所以就有分批这个概念。即，我们不会每次只算一条特征，而是把特征打批，一次计算多个，公式如下：&lt;/p&gt;
&lt;div class=&quot;math-display&quot;&gt;\[\begin{bmatrix}
h_{1,1} &amp;amp; h_{1,2} &amp;amp; h_{1,3} \\
h_{2,1} &amp;amp; h_{2,2} &amp;amp; h_{2,3} \\
h_{3,1} &amp;amp; h_{3,2} &amp;amp; h_{3,3} \\
h_{4,1} &amp;amp; h_{4,2} &amp;amp; h_{4,3} \\
h_{5,1} &amp;amp; h_{5,2} &amp;amp; h_{5,3}
\end{bmatrix} = \begin{bmatrix}
x_{1,1} &amp;amp; x_{1,2} \\
x_{2,1} &amp;amp; x_{2,2} \\
x_{3,1} &amp;amp; x_{3,2} \\
x_{4,1} &amp;amp; x_{4,2} \\
x_{5,1} &amp;amp; x_{5,2}
\end{bmatrix}_{\text{Data (5 days)}}
\cdot
\begin{bmatrix}
w_{1,1} &amp;amp; w_{2,1} &amp;amp; w_{3,1} \\
w_{1,2} &amp;amp; w_{2,2} &amp;amp; w_{3,2}
\end{bmatrix}_{W^T}
+
\begin{bmatrix}
b_1 &amp;amp; b_2 &amp;amp; b_3
\end{bmatrix}_{\text{Bias}}\]&lt;/div&gt;&lt;p&gt;，&lt;/p&gt;
&lt;p&gt;即：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(h_{\text{单层输出}} = xW^T + b\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;用代码表达如下：&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;SimpleFCN&lt;/span&gt;(nn.Module):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;__init__&lt;/span&gt;(&lt;span class=&quot;params&quot;&gt;self&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;(SimpleFCN, &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;).__init__()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.fc1 = nn.Linear(&lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.fc2 = nn.Linear(&lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;forward&lt;/span&gt;(&lt;span class=&quot;params&quot;&gt;self, x&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        x = torch.sigmoid(&lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.fc1(x))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        x = torch.sigmoid(&lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.fc2(x))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; x&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;CNN和RNN&quot;&gt;CNN和RNN&lt;/h2&gt;&lt;p&gt;有了FCN之后，理论上我们可以通过大量的数据训练出任何我们想要得到的结果。但是因为FCN是全连接的，这里面主要会有&lt;strong&gt;三个&lt;/strong&gt;缺点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;参数冗余与计算量大&lt;/strong&gt;：默认所有的神经元与上一层所有神经元都有连接，这会导致参数量的急剧增加。特别是当&lt;strong&gt;输入数据的维度很高&lt;/strong&gt;（如高分辨率图像）或网络层数加深时，容易引起&lt;strong&gt;参数爆炸&lt;/strong&gt;，直接撑爆计算机的内存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;忽略局部特征&lt;/strong&gt;：因为FCN每次计算必须感知所有输入，它没有”&lt;strong&gt;空间局部性&lt;/strong&gt;“的概念。这意味着它无法像人眼一样，先关注局部的特征（如图像中的线条、纹理），再组合成整体，而是”囫囵吞枣”地处理所有信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没办法感知”顺序”&lt;/strong&gt;：在FCN的逻辑里，所有的输入特征是平行的，没办法感知哪个”先来”，哪个”后到”。 譬如当我们把一个Sentence作为Input丢到FCN里，FCN&lt;strong&gt;无法有效捕捉到Token前后的时序依赖关系&lt;/strong&gt;，难以理解上下文的语义。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;CNN&quot;&gt;CNN&lt;/h3&gt;&lt;p&gt;CNN即卷积神经网络（Convolutional Neural Network），它的核心是有一个卷积层，这个卷积层不会像FCN一样每个神经元都连接所有的参数，而是”选择性”地连接。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/8f5c1f10_img8.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;假如说有一张3&lt;em&gt;3像素的图片，如果我们使用FCN来识别该图片，就只能把3&lt;/em&gt;3的像素铺平成9个参数，然后让这个9个参数进行全连接的线性运算，单一神经元的数学表达如下：&lt;/p&gt;
&lt;div class=&quot;math-display&quot;&gt;\[\begin{bmatrix}
  x_1 &amp;amp; x_2 &amp;amp; x_3 \\
  x_4 &amp;amp; x_5 &amp;amp; x_6 \\
  x_7 &amp;amp; x_8 &amp;amp; x_9
  \end{bmatrix}
  \xrightarrow{\text{Flatten}}
  [x_1, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9]\]&lt;/div&gt;&lt;p&gt;,&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(h = \sigma(w_{1} x_1 + w_{2} x_2 + ... + w_n x_n + ... + w_9 x_9 + b) \\\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;因为铺平的原因，导致参数之间几乎丧失了位置和空间信息，所以用FCN不仅会导致参数爆炸，同时也会使得识别参数的精确度受到影响。&lt;/p&gt;
&lt;p&gt;对于图片来说，要想保留位置信息，我们就不能把输入像素铺平，而是要考虑像素参数之间的关系，同时为了防止每次计算都计算所有像素点，我们需要设置一个窗口，每个神经元只计算窗口内的值，假定我们的窗口是2x2，那么可以用如下表示：&lt;/p&gt;
&lt;div class=&quot;math-display&quot;&gt;\[\text{Kernel} =
  \begin{bmatrix}
  w_{1,1} &amp;amp; w_{1,2} \\
  w_{2,1} &amp;amp; w_{2,2}
  \end{bmatrix}\]&lt;/div&gt;&lt;p&gt;这里的Kernel即为卷积核，此时神经元的表达如下：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(h = \sigma(w_{1,1} x_1 + w_{1,2} x_2 + w_{2,1} x_4 + w_{2,2} x_5 + b) \\\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;可以看到，对于卷积核的神经元来说，有两个特点，第一是不会识别所有的参数；第二是可以精确感知到位置相关的信息。&lt;/p&gt;
&lt;p&gt;卷积核的核心就是关心局部的特征而不是所有的特征。我们可以把卷积核理解成一个局部特征，假如说&lt;span class=&quot;math-inline&quot;&gt;\(K_h\)&lt;/span&gt;拥有2&lt;em&gt;2的参数，他的作用是专门识别”竖线”。&lt;span class=&quot;math-inline&quot;&gt;\(K_h\)&lt;/span&gt;会上下左右平移遍历每一个2&lt;/em&gt;2的像素点，找到可能是”竖线”的像素图块。&lt;/p&gt;
&lt;p&gt;一张图片不止有一个特征，所以我们会有多个卷积核（&lt;span class=&quot;math-inline&quot;&gt;\(K_1,..., K_n\)&lt;/span&gt;），每个卷积核分别代表不同的特征，譬如有些卷积核负责识别线条、有些负责识别颜色，等等。&lt;/p&gt;
&lt;p&gt;一个简单的CNN代码如下所示：&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch.nn &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; nn&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CNN_Model&lt;/span&gt;(nn.Module):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;__init__&lt;/span&gt;(&lt;span class=&quot;params&quot;&gt;self&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;().__init__()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 1. 卷积层 (眼睛)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# in_channels=1: 因为是黑白图，只有1层深&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# out_channels=32: 我们用 32 个不同的卷积核(手电筒)去扫描，提取32种不同的特征&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# kernel_size=3: 卷积核大小是 3x3&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.conv1 = nn.Conv2d(in_channels=&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, out_channels=&lt;span class=&quot;number&quot;&gt;32&lt;/span&gt;, kernel_size=&lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 2. 激活函数 (当然还要用 ReLU)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.relu = nn.ReLU()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 3. 池化层 (压缩)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 2x2 的窗口，把图片长宽各缩小一半&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.pool = nn.MaxPool2d(kernel_size=&lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 4. 全连接层 (大脑)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 经过上面的卷积和池化，图片变小了，但在深度上变厚了(32层)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 这里需要算一下剩下的特征数量，连接到分类器&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.fc = nn.Linear(in_features=&lt;span class=&quot;number&quot;&gt;32&lt;/span&gt; * &lt;span class=&quot;number&quot;&gt;13&lt;/span&gt; * &lt;span class=&quot;number&quot;&gt;13&lt;/span&gt;, out_features=&lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;forward&lt;/span&gt;(&lt;span class=&quot;params&quot;&gt;self, x&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 保持图片的 2D 结构进行处理&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        x = &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.conv1(x)  &lt;span class=&quot;comment&quot;&gt;# 扫描&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        x = &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.relu(x)   &lt;span class=&quot;comment&quot;&gt;# 激活&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        x = &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.pool(x)   &lt;span class=&quot;comment&quot;&gt;# 压缩&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 展平，交给全连接层做最后决定&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        x = x.view(x.size(&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;), -&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        x = &lt;span class=&quot;variable language_&quot;&gt;self&lt;/span&gt;.fc(x)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; x&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;model = CNN_Model()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;print&lt;/span&gt;(model)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;RNN&quot;&gt;RNN&lt;/h3&gt;&lt;p&gt;FCN将用户输入的特征二次加工成通用特征、CNN进一步处理了部分特征关联的问题。但是FCN和CNN都没有解决不同时间特征关联的问题。请考虑下面一个场景：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在做机器翻译的时候，我们尝试将”Book a hotel” 和 “I like book” 翻译成中文&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果我们直接使用FCN训练上面的句子，那就需要把 “Book a hotel” 的每个token都作为参数进行训练，这固然是可行的，但是一旦翻译的句子变成了 “I like book”，原来训练的FCN模型就完全不可用（这是因为FCN的入参是固定的）。所以在输入变化的场景，只靠FCN是完全不行的。&lt;/p&gt;
&lt;p&gt;既然整个句子训练不可行，所以我们就必须将句子拆分开成单词来训练，即模型的输入不能像FCN一样全部输入，而是滚动的输入。但是因为 “book” 在不同句子中语义是不一样的，所以我们在输入当前单词的时候，也需要把前面一些单词记录下来，也作为输入给到模型。&lt;/p&gt;
&lt;p&gt;所以，我们能否设计一个 1）动态感知输入token；2）不仅能够感知当前token，还可以感知到以前的token。这个就是 &lt;strong&gt;RNN (Recurrent Neural Network)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/25f4cfb9_img9.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;所谓的RNN，核心就是在原来FCN的基础上，增加了循环的机制，可以感知上一次的输出。它的数学表达如下：&lt;/p&gt;
&lt;div class=&quot;math-display&quot;&gt;\[h_{t-1} = \sigma(W_{xh} \cdot x_{t-1} + b )\\
h_t = \sigma(\underbrace{W_{xh} \cdot x_t}_{\text{处理当前输入}} + \underbrace{W_{hh} \cdot h_{t-1}}_{\text{处理过去记忆}} + b)\]&lt;/div&gt;&lt;p&gt;通过数学公式可以看出来，RNN的核心就是将上一次的输入计算的结果作为下一次的输入代入进来。Python代码如下所示：&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch.nn &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; nn&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 设定参数 ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;input_size = &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;   &lt;span class=&quot;comment&quot;&gt;# 输入特征维度 (比如今天的10个财经指标)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;hidden_size = &lt;span class=&quot;number&quot;&gt;20&lt;/span&gt;  &lt;span class=&quot;comment&quot;&gt;# 记忆容量 (隐藏层神经元数量)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 定义权重 (这就是 RNN 模型本体) ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 1. 处理输入的权重 (对应 x_t)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;W_xh = nn.Linear(input_size, hidden_size)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 2. 处理记忆的权重 (对应 h_t-1) &amp;lt;--- RNN 独有的！&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;W_hh = nn.Linear(hidden_size, hidden_size)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 模拟时间序列数据 (Sequence) ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 假设有 5 天的数据 (Time steps = 5)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;inputs = [torch.randn(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, input_size) &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; _ &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;range&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;5&lt;/span&gt;)]&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 初始化记忆 (h_0) ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 一开始脑子是空的&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;h_t = torch.zeros(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, hidden_size)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;开始时间循环...\n&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- RNN 的核心循环 ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; i, x_t &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;enumerate&lt;/span&gt;(inputs):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# 公式: h_t = tanh( W_xh * x + W_hh * h_prev )&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# 1. 现在的输入产生的反应&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    current_input_effect = W_xh(x_t)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# 2. 过去的记忆产生的反应&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    past_memory_effect = W_hh(h_t) &lt;span class=&quot;comment&quot;&gt;# 注意这里用的是上一轮的 h_t&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# 3. 融合 (相加) 并 激活&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# Tanh 是 RNN 最常用的激活函数，因为它输出在 -1 到 1 之间，能防止数值爆炸&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    h_t = torch.tanh(current_input_effect + past_memory_effect)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;f&quot;第 &lt;span class=&quot;subst&quot;&gt;{i+&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;}&lt;/span&gt; 天: 更新了隐藏状态 (记忆)。部分数值: &lt;span class=&quot;subst&quot;&gt;{h_t[&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;][:&lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;].detach().numpy()}&lt;/span&gt;...&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;\n循环结束。最终的 h_t 包含了过去 5 天的所有累积信息。&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;但是有一个细节，&lt;strong&gt;假如权重是0.9&lt;/strong&gt;，那么随着传递层数的增加，&lt;span class=&quot;math-inline&quot;&gt;\(0.9^{n}\)&lt;/span&gt;&lt;strong&gt;会趋近于0&lt;/strong&gt;。说明RNN不能记忆太长的输入，过长的输入会令RNN”遗忘”（这被称为梯度消失问题）。所以又诞生了 &lt;strong&gt;LSTM&lt;/strong&gt; 来通过 &lt;strong&gt;Input Gate、Output Gate、Forget Gate&lt;/strong&gt; 来决策记忆的存储时机，更加精准地控制模型的遗忘频率。&lt;/p&gt;
&lt;h1 id=&quot;模型的参数究竟是什么？&quot;&gt;模型的参数究竟是什么？&lt;/h1&gt;&lt;p&gt;从上面的FCN、CNN和RNN模型，我们可以知道，模型本质上就是&lt;span class=&quot;math-inline&quot;&gt;\(y = \sigma(kx+b)\)&lt;/span&gt;，所以，所谓的参数就是&lt;span class=&quot;math-inline&quot;&gt;\(k\)&lt;/span&gt;（权重，即 w）和 b（偏置）。只不过真实的模型有 n 个 x，和 m 个 y。当 n$和 m（以及中间的隐层神经元）越多，权重和偏置就越多，经过训练的模型也就&lt;strong&gt;越&lt;/strong&gt;“聪明”，这就是后来业界”大模型”的通俗理解。&lt;/p&gt;
&lt;p&gt;训练模型，也就是通过各种数学方案，计算出最有效的权重和偏置。拿GPT-3举例，它有175B（&lt;strong&gt;1750亿&lt;/strong&gt;）的参数，说明它的各个神经元中的 w 和 b加到一起是1750亿个。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TODO-超参数。&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;模型是如何理解自然语言的？&quot;&gt;模型是如何理解自然语言的？&lt;/h1&gt;&lt;p&gt;到目前为止，我们知道了模型是如何做推理的——即给定一个x的输入（x为张量），通过一系列公式的运算，得到一个输出。那么问题来了，无论是FCN、RNN还是CNN，它们只能做浮点运算，限定的输入都一定是一组数字，这对于普通的数学预测还好说。但是，对于目前如火如荼的自然语言处理来说，我们如何把自然语言转化成数字让模型理解呢？&lt;/p&gt;
&lt;h2 id=&quot;One-Hot&quot;&gt;One-Hot&lt;/h2&gt;&lt;p&gt;最简单的理解方式，就是我们可以通过boolean数组的映射解决。给定一个固定长度的boolean数组，通过在不同的index上标识为true来定义不同的token。假设你的词表里只有5个词：&lt;code&gt;[&quot;我&quot;, &quot;爱&quot;, &quot;学习&quot;, &quot;AI&quot;, &quot;猫&quot;]&lt;/code&gt;。 “学习”这个词的表示就是：&lt;code&gt;[0, 0, 1, 0, 0]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;One-hot有两个致命问题，导致它无法用于 LLM：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;稀疏性与维度灾难：&lt;/strong&gt; 真实的词表有几万甚至几十万个词。每个词都是一个长度为 50,000 的向量，里面有 49,999 个 0。这极其浪费计算资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;由于没有语义关联（Semantic Meaning）：&lt;/strong&gt; 在 One-Hot 空间里，所有词之间的距离都是一样的。**”猫”&lt;strong&gt;和&lt;/strong&gt;“狗”&lt;strong&gt;的距离，与&lt;/strong&gt;“猫”&lt;strong&gt;和&lt;/strong&gt;“冰箱”**的距离一样远。模型无法知道”猫”和”狗”是相似的。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;Embedding&quot;&gt;Embedding&lt;/h2&gt;&lt;p&gt;我们可以通过embedding的方式，将one-hot的高维稀疏向量压缩成一个低维稠密向量。 例如，将”猫”映射到一个 512 维的向量中：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(\text{Embedding(cat)} = [0.21, -0.54, 0.03, ..., 0.99]\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;在这个新的向量空间中，&lt;strong&gt;语义相似的词，在几何空间上的距离会更近&lt;/strong&gt;。举个例子，我们可以近似的认为&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(&quot;国王&quot; - &quot;男人&quot; + &quot;女人&quot; \approx &quot;女王&quot;\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;那么，我们如何得到这样一组能记住词汇关系的稠密向量呢？方法很简单，就是通过FCN进行无监督训练和转换即可。下面举一个例子：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;计算”机器 学习 改变 了 世界”的5维one-hot向量，转为3维的稠密向量&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;定义这五个token的one-hot向量，如_学习_的one-hot向量是&lt;span class=&quot;math-inline&quot;&gt;\(x = [0, 1, 0, 0, 0]\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;定义一个线性回归网络，W为5x3结构，将”学习”的x代入如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;math-display&quot;&gt;\[h = x \cdot W_{in} = [0, 1, 0, 0, 0] \cdot
\begin{bmatrix}
w_{0,0} &amp;amp; w_{0,1} &amp;amp; w_{0,2} \\
\mathbf{w_{1,0}} &amp;amp; \mathbf{w_{1,1}} &amp;amp; \mathbf{w_{1,2}} \\
w_{2,0} &amp;amp; w_{2,1} &amp;amp; w_{2,2} \\
\vdots &amp;amp; \vdots &amp;amp; \vdots \\
w_{4,0} &amp;amp; w_{4,1} &amp;amp; w_{4,2}
\end{bmatrix}_{w初始为随机浮点数}\]&lt;/div&gt;&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;接着，通过&lt;span class=&quot;math-inline&quot;&gt;\(h\)&lt;/span&gt;与另外一个转置矩阵&lt;span class=&quot;math-inline&quot;&gt;\(W_{out}\)&lt;/span&gt;计算点积：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(z = \sigma(h \cdot W_{out} = h \cdot [u_0, u_1, u_2, u_3, u_4])\)&lt;/span&gt;&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;此时能够得到”学习”与原句子每个token之间关联的概率。假如说算出来的点积结果如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(\hat{z} = [0.05, \quad 0.02, \quad \mathbf{0.80}, \quad 0.01, \quad 0.12]\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;则表明”学习”后面接”改变”的概率是80%，但是我们希望是100%，此时就需要通过反向传播更新W的数值。最后，经过多轮训练后，&lt;span class=&quot;math-inline&quot;&gt;\(W_{input}\)&lt;/span&gt;即是我们最终的embedding向量。&lt;/p&gt;
&lt;h1 id=&quot;机器是如何学习的？&quot;&gt;机器是如何学习的？&lt;/h1&gt;&lt;p&gt;在前面，我们已经多次强调过，我们需要训练模型让模型自主学习。那么模型是如何学习的呢？&lt;/p&gt;
&lt;p&gt;在深入了解模型的学习逻辑之前，我们要先理解两个术语。分别是前向传播（Forward Propagation）和反向传播（Backpropagation）。&lt;/p&gt;
&lt;h2 id=&quot;前向传播&quot;&gt;前向传播&lt;/h2&gt;&lt;p&gt;前向传播的解释其实很简单，我们在上文中进行FCN、CNN、RNN的过程，就叫前向传播，用公式表示如下：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(y_{pred} = \text{Model}(x)\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;在前向传播的计算过程中，参数（Weight 和 bias）都是已知和给定的。事实上，当我们在使用GPT或者Gemini的时候，模型在输出答案的过程，就叫前向传播。&lt;/p&gt;
&lt;h2 id=&quot;反向传播&quot;&gt;反向传播&lt;/h2&gt;&lt;p&gt;反向传播与前向传播相反，指的是再已知&lt;span class=&quot;math-inline&quot;&gt;\(y_{pred},y_{true},LearningRate\)&lt;/span&gt;的情况下，计算出参数&lt;span class=&quot;math-inline&quot;&gt;\(Weight, Bais\)&lt;/span&gt;的过程。&lt;/p&gt;
&lt;p&gt;为了定义&lt;span class=&quot;math-inline&quot;&gt;\(y_{pred}\)&lt;/span&gt;和&lt;span class=&quot;math-inline&quot;&gt;\(y_{true}\)&lt;/span&gt;之间究竟相差多少，我们一般通过损失函数（Loss Function）来衡量，也就是我们训练模型常说的Loss，Loss越大，说明模型的结果越差。常见的损失函数就是均方误差(Meaning Squre Error)：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(Loss = (y_{pred} - y_{true})^2\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;为了让Loss变小，我们就需要反向传播，利用梯度下降（Gradient Descent）算法，通过微分计算出新的权重，公式如下：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;math-inline&quot;&gt;\(W_{new} = W_{old} - \text{learning\_rate} \times \text{Gradient}\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Learning_rate就是变化的步长，决定了每次训练权重的变化幅度。关于Gradient如何计算，大家可以自行搜索，此处不详细描述，一个反向传播的例子如下：&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;51&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;52&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;53&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;54&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;55&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch.nn &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; nn&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch.optim &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; optim&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; plt&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 1. 准备数据 (真值) ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 假设真实规律是: y = x * 0.5 + 0.2&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 我们生成 100 个数据点&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;X = torch.rand(&lt;span class=&quot;number&quot;&gt;100&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;)  &lt;span class=&quot;comment&quot;&gt;# 随机生成 0~1 之间的温度&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;y_true = X * &lt;span class=&quot;number&quot;&gt;0.5&lt;/span&gt; + &lt;span class=&quot;number&quot;&gt;0.2&lt;/span&gt;  &lt;span class=&quot;comment&quot;&gt;# 这是我们要逼近的&quot;靶心&quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 2. 搭建模型 (射手) ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 一个最简单的线性层: y = w * x + b&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 初始时，w 和 b 都是随机生成的，它根本不知道 0.5 和 0.2 是什么&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;model = nn.Linear(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;f&quot;初始随机权重 w: &lt;span class=&quot;subst&quot;&gt;{model.weight.item():&lt;span class=&quot;number&quot;&gt;.4&lt;/span&gt;f}&lt;/span&gt;, 偏置 b: &lt;span class=&quot;subst&quot;&gt;{model.bias.item():&lt;span class=&quot;number&quot;&gt;.4&lt;/span&gt;f}&lt;/span&gt;&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 3. 定义工具 ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Loss Function (裁判): 均方误差&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;criterion = nn.MSELoss()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Optimizer (教练): 随机梯度下降 (SGD)，学习率 lr=0.1&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;optimizer = optim.SGD(model.parameters(), lr=&lt;span class=&quot;number&quot;&gt;0.1&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 4. 开始训练循环 (练习射箭 100 次) ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;loss_history = []&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; epoch &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;built_in&quot;&gt;range&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;100&lt;/span&gt;):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# A. 前向传播 (射出一箭)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    y_pred = model(X)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# B. 计算 Loss (测量差距)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    loss = criterion(y_pred, y_true)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    loss_history.append(loss.item())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# C. 反向传播 (寻找原因)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# 这行代码会自动计算所有权重的梯度&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    optimizer.zero_grad() &lt;span class=&quot;comment&quot;&gt;# 清空上一次的梯度&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    loss.backward()       &lt;span class=&quot;comment&quot;&gt;# &amp;lt;--- 核心！反向传播发生在这里&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;# D. 更新权重 (纠正姿势)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    optimizer.step()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# --- 5. 结果展示 ---&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;f&quot;训练后学到的权重 w: &lt;span class=&quot;subst&quot;&gt;{model.weight.item():&lt;span class=&quot;number&quot;&gt;.4&lt;/span&gt;f}&lt;/span&gt; (真实值是 0.5)&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;f&quot;训练后学到的偏置 b: &lt;span class=&quot;subst&quot;&gt;{model.bias.item():&lt;span class=&quot;number&quot;&gt;.4&lt;/span&gt;f}&lt;/span&gt; (真实值是 0.2)&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 画图：Loss 是如何下降的&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;plt.figure(figsize=(&lt;span class=&quot;number&quot;&gt;8&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;4&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;plt.plot(loss_history, color=&lt;span class=&quot;string&quot;&gt;&apos;red&apos;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;plt.title(&lt;span class=&quot;string&quot;&gt;&apos;Loss Curve: Error drops as the model learns&apos;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;plt.xlabel(&lt;span class=&quot;string&quot;&gt;&apos;Training Iterations (Epochs)&apos;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;plt.ylabel(&lt;span class=&quot;string&quot;&gt;&apos;Loss (Error)&apos;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;plt.grid(&lt;span class=&quot;literal&quot;&gt;True&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;plt.show()&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h1 id=&quot;为什么得显卡者得天下？&quot;&gt;为什么得显卡者得天下？&lt;/h1&gt;&lt;p&gt;在探究这个问题之前，我们先分清楚CPU和GPU的区别：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CPU&lt;/strong&gt;：CPU的核心少而强大（如我们平常的服务器核心是8核），负责复杂的计算和逻辑分支处理（在计算单元的基础上增加cache和控制单元等）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPU&lt;/strong&gt;：GPU的核心多而简单（随便一个就是4096核），每个核心能力有限，只能负责简单的加减乘除，无法处理复杂逻辑。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从模型推理和模型训练可以得知，无论是前向传播还是反向传播，都会涉及到大量的向量乘积运算、以及微分（求导）—— 它们本质上都是简单的浮点运算。这些简单的浮点运算基本不会&lt;strong&gt;互相&lt;/strong&gt;干扰，所以它们天生就是可以被”并行”计算的，所以它极适合GPU架构。&lt;/p&gt;
&lt;p&gt;举个例子，如果我们要计算两个 &lt;strong&gt;1000x1000&lt;/strong&gt; 的矩阵，这意味着我们要进行 &lt;strong&gt;10亿次级别&lt;/strong&gt; 的乘加运算。对于一个8线程的CPU来讲，即使8个并行处理，它也还要处理非常久。而GPU拥有更多的核心，所以处理显然也就更快。&lt;/p&gt;
&lt;h1 id=&quot;参考资料&quot;&gt;参考资料&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;李宏毅资料：&lt;a href=&quot;https://www.youtube.com/playlist?list=PLJV_el3uVTsMhtt7_Y6sgTHGHp1Vb2P2J&quot;&gt;油管&lt;/a&gt;,、&lt;a href=&quot;https://www.bilibili.com/video/BV1TAtwzTE1S/?spm_id_from=333.337.search-card.all.click&amp;amp;vd_source=f22e07c1e9ae0985f92cf3a4f47ea7f8&quot;&gt;B站&lt;/a&gt;&lt;ol&gt;
&lt;li&gt;ml:&lt;a href=&quot;https://speech.ee.ntu.edu.tw/~hylee/ml/2021-spring.php&quot;&gt;https://speech.ee.ntu.edu.tw/~hylee/ml/2021-spring.php&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;generative: &lt;a href=&quot;https://speech.ee.ntu.edu.tw/~hylee/genai/2024-spring.php&quot;&gt;https://speech.ee.ntu.edu.tw/~hylee/genai/2024-spring.php&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ML 2025: &lt;a href=&quot;https://speech.ee.ntu.edu.tw/~hylee/ml/2025-spring.php&quot;&gt;https://speech.ee.ntu.edu.tw/~hylee/ml/2025-spring.php&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Gemini&lt;/li&gt;
&lt;li&gt;datawhale：&lt;a href=&quot;https://github.com/datawhalechina&quot;&gt;https://github.com/datawhalechina&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item>
<item><title>在香港科技大学求学：工作后的教育选择与体验</title><link>https://wxxlamp.cn/2025/10/07/why-graduated-to-hkust/</link><guid isPermaLink="true">https://wxxlamp.cn/2025/10/07/why-graduated-to-hkust/</guid><pubDate>Tue, 07 Oct 2025 02:36:00 GMT</pubDate><description>以工作后赴港就读港科大一周年为契机，对比香港与内地在制度、金融、社会福利差异，聚焦两地高校教育体系异同，呈现香港留学真实体验。</description><content:encoded>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;&lt;p&gt;时间飞逝，从2024年9月去香港读master到现在，已经整整一年了。虽然这一年在香港呆的时间也不算久（满打满算可能就大几十天吧），但是香港独特的城市特色已经深深的震撼到我。&lt;/p&gt;
&lt;p&gt;我是一个典型的小镇做题家，初高中的学习就没有离开过县城。印象中第一次去省会还是高考结束后参加某985大学的高校专项考试，而第一次出省更是在大学以后，18岁大一那年暑假和同学一起去的上海。至于出国（境），说来惭愧，更是从工作到读Master之前从来没有过。&lt;/p&gt;
&lt;p&gt;所以我想借着学习一周年这个机会，从一个工作后再留学的打工人角度，谈谈我留学一年的经历。本文是系列文章第一篇，主要介绍香港、尤其是香港教育与内地的差异。也欢迎对香港留学感兴趣的朋友私我了解详情。&lt;/p&gt;
&lt;h1 id=&quot;香港与内地的差异在哪里？&quot;&gt;香港与内地的差异在哪里？&lt;/h1&gt;&lt;p&gt;本段不会聚焦于简单的生活差异（如语言、交通等），核心会从香港定位和一国两制的背景出发，聊聊香港与大陆的不同。&lt;/p&gt;
&lt;h2 id=&quot;从一国两制看香港&quot;&gt;从一国两制看香港&lt;/h2&gt;&lt;p&gt;去年从开学去香港之后，刚开始只是对不同的环境有点好奇（譬如任何地方的标语都是繁体和英文，从来没有见到过普通话和拼音），后来发现香港的制度确实和内地是有本质的区别的。&lt;/p&gt;
&lt;p&gt;所谓一国两制，即香港的外交、主权、军事由中央政府统一管理，而其他的譬如立法、司法、检察、言论和金融等其他制度，则由香港政府自行决策。所以进入香港以后，会发现香港的言论管控、意识形态和内地完全不一样。笔者印象中，罗永浩和周鸿祎都有过被内地警察传唤跑路香港的经历，这也是因为香港司法独立，内地警察没办法跨境执法的原因。同时，香港的金融制度也更加贴近西方，譬如支付习惯，几乎和欧美一模一样。金融制度的相似性也是为什么香港的外国人比较多的原因之一。同时，香港身份更容易得到其他国家和地区的认可，很多同学借助香港的身份证申请日签、欧美签就会特别容易。&lt;/p&gt;
&lt;p&gt;高中课本就告诉我们，我们是中国特色社会主义国家，而香港在97年之后，依然是延续英国殖民统治时期的资本主义制度。在政府选举上，基本上符合法定资格（遵守《基本法》）的社会各阶层人士都可以依法参与选举，我在往返学校的小巴上就经常见到选举人张贴类似“pick me”的广告。同时，香港民众可以对政府的措施在各个渠道提出意见（不能违反《基本法》），即所谓的“法无禁止即可为”。作为资本主义地区，香港很多事务都是资本运作，如员工的强积金就是由符合资质的私营强积金受托人管理，这和内地的国营企业，政府直管有着本质区别。甚至港币也是由三大私营银行在政府的授权下，锚定美元发行（即银行上缴美元给港府，获的许可后，再按照固定汇率发行港币），这和内地由央行统一发行人民币也有着本质的不同。&lt;/p&gt;
&lt;h2 id=&quot;东方之珠为什么是香港&quot;&gt;东方之珠为什么是香港&lt;/h2&gt;&lt;p&gt;香港被誉为“东方之珠”，也是内地和境外交流的金融中心。因为之前英国殖民的原因，同时叠加近年来政府的有意推进，香港成为亚洲TOP级别的金融中心，几乎全球主要金融机构都会把亚洲总部开设到香港（当然，近年来各种原因导致部分金融机构的总部搬迁到新加坡，新加坡正在挑战香港，建立新的金融秩序）。&lt;/p&gt;
&lt;p&gt;每年有源源不断的外汇储备通过香港交割到内地，同时也有很多人民币经过香港的金融中心汇兑成美元。内地几乎所有的国际化公司（包括大家耳熟能详的互联网公司）都会在香港开立分公司，借助香港的国际结算网络、多币种融资渠道等开展业务，大部分交易结算资金通过香港银行体系流转。近年来很多企业都谋求出海，甚至可以说，香港一定是这些内地公司出海的第一站——不是将商品卖到香港，而是借助香港无外汇管制的优势，进行跨境资金调配，或兑换成港元、美元等币种用于国际业务结算、资产配置。&lt;/p&gt;
&lt;p&gt;所以，香港的金融产业极其发达，国内很多大型公司的金融、投资、法务相关部门都在香港设立。之前听一个鹅厂内部的朋友的朋友说，他每年要去香港去给Pony汇报一次投资情况，这也侧面证明了香港的金融地位。但是对于笔者从事的IT来讲，香港就逊色很多，技术栈老旧、工资低是香港IT的代名词。只有银行的技术部或者量化技术，才是香港IT的唯一出路，而能进这些公司，和技术关系又能有多大呢？&lt;/p&gt;
&lt;p&gt;得益于香港金融中心的地位，深圳也凭借和香港隔海相望的地理位置，发展的越发迅速。稍微留意下招聘市场就会发现，深圳的跨境贸易公司和跨境支付公司超级无敌多。同时，因为香港没有外汇管制且和深圳通关便利，很多深圳有炒股经验的同事会将一定量的人民币换成港币或者美元，进一步优化自己的资产配置。&lt;/p&gt;
&lt;h2 id=&quot;香港工作和生活&quot;&gt;香港工作和生活&lt;/h2&gt;&lt;p&gt;去香港次数多了之后，会发现香港的社会福利和老龄化设施确实和内地不一样。&lt;/p&gt;
&lt;p&gt;譬如会给上了年龄的老人发放高龄津贴和长者生活津贴。工作党需要缴纳的税收相对来说比较低（阶梯计税，最高17%），除了交税之外，只需要缴纳5%的强积金即可（强积金体系类似于内地的养老金体系）。最令我印象深刻的一点，香港的基础设施对老人和残疾人非常友好。公交车会配备轮椅上车通道，一旦有坐轮椅的人在公交站等车，司机会立刻下车把专用通道打开让轮椅上车。同时，香港的盲道建设也非常齐全，我在上下学的路上曾经不止一次看到盲人拄着拐杖在路上行走。香港的老龄化甚至不只是基础设置的建设，更是深入到普通群众的心中，很多人宁愿站着也不愿意坐地铁公交上的老人座位。&lt;/p&gt;
&lt;p&gt;令我震惊的是，香港人的规则意识很强，即使电梯人非常多，香港人还是会排队站到电梯右边，把左边通道留给有急事的人（人特别多的情况除外）。记得有一次在厕所大号排队，香港人竟然在门口就开始排队，这样就保证了谁先到厕所谁先用坑（而不是每个人都站在坑位门口，按照坑位排队）。当大家都遵守一套规则之后，普通人会更公平的享受基础服务，整个社会的运转也会更高效。&lt;/p&gt;
&lt;p&gt;但是，没有任何地方是完美无缺的。很多人都知道香港的平均工资非常高，甚至随处可见的餐厅服务员月薪就在15,000-25,000 HKD左右。但是香港的消费贼高，先不提住房，就说简单的吃饭，我在学校食堂的一顿很普通的三送饭，就要40-50 HKD左右。甚至外面的一个苍蝇馆子，随便点几个菜就人均200 HKD起，对于普通人来说根本伤不起。有时候从学校回深圳，大概50KM路程，打车就要400 HKD。不过话说回来，香港的公共交通非常发达，基本的出行都可以靠地铁和公交完成。&lt;/p&gt;
&lt;p&gt;部分内地人可能经历过香港人的歧视。我在有一次坐小巴回家的路上，因为听错司机说的粤语，就被司机轻蔑的说道：“这里是香港，不是大陆”。那一瞬间，心里说不生气是假的，但是我也知道，这是司机的无能狂怒罢了。了解香港的朋友告诉我，随着一国两制实践的加快和香港高端人才的不断引进，香港有很多上了年纪的蓝领对内地人还是有敌对意识的，在他们心里，还是觉得内地是20年前的那个内地。殊不知，时代风云变幻莫测，中国正在比任何时候都变的强大。&lt;/p&gt;
&lt;p&gt;但是不得不承认，客观上来讲，内地和香港在某些地方还是有明显差距的。笔者认为，这些差距的背后是14亿和700万人的区别，人数的不同，管理难度也会呈指数级别上升。时代在发展，党的坚强领导、社会主义制度的优越性，都将成为东方巨龙的发展引擎，我们的祖国，一定会变得更好！&lt;/p&gt;
&lt;h1 id=&quot;香港的大学教育和内地有区别吗？&quot;&gt;香港的大学教育和内地有区别吗？&lt;/h1&gt;&lt;p&gt;虽然大学本科毕业已经是三年前的事情了，但是再次去香港读书，还是能感受到两边教育体系的巨大差别。&lt;/p&gt;
&lt;p&gt;内地的教育更注重普惠，让每一个孩子即使再穷都能上得起大学是政府首要的目标，同时内地政府也会花很大精力尽可能弥补能力有缺陷人的教育不足。但是不得不说，内地的学生太多了，同时也因为内地发展太快，师生比相对来说比较低。香港在师生比方面做的会稍微好一点，而且众所周知，香港的教育更加偏向“精英化”，学生们的发展会更加多样一点。&lt;/p&gt;
&lt;p&gt;下面，我就从自己的个人经历，简单对比下内地211院校和港三院校教育体系上的区别。&lt;/p&gt;
&lt;h2 id=&quot;学校信息系统建设&quot;&gt;学校信息系统建设&lt;/h2&gt;&lt;p&gt;记得我在整个本科生涯，不管是学校学院通知、还是活动、亦或是课程作业，全部是通过QQ（现在可能是微信了）群聊沟通的。但是由于香港的学习工作方式更加偏重西式，所以很多事情都是通过邮件的形式通知的，譬如放假后学校穿梭巴士的安排、放假时间、教授讲座、维修信息等等各种内容事无巨细。学生平均每天收到几十封相关邮件都很正常。&lt;/p&gt;
&lt;p&gt;群通知和邮件的方式孰优孰略见仁见智，但是香港课堂的信息化会比内地高出不少。老师的联系方式、课程的信息、布置的作业（包括deadline等）都可以在网站或者canvas上查到。学生的作业写完后也是通过canvas直接将电子版上传即可，这块我的感触尤其深刻。记得上本科的时候，几乎所有的作业（计算题、coding等）都是写到纸上交给学习委员上交，错过了学委收作业的时间，就只能自己交给老师，很多同学因为找不到学委错过deadline的情况也时有发生。印象中本科时候的的成绩和排名都是发给班长，班长直接post到群公告里，很多考的差的同学直接社死。而在香港读master的时候，老师只会把成绩、排名、班级前后1/4成绩线公布到该同学自己的canvas上，保障了每位同学的隐私。&lt;/p&gt;
&lt;p&gt;除了通知、上课之外，香港学校官网的信息化做的也特别好，任何信息都可以在学校内网找到，包括课程成绩、就业信息、学费缴纳、毕业信息等等。由于上课期间不经常在香港，所以最令我感到方便的是我可以随时查看我学分的选修情况，可以直接通过网站信息来check我的毕业进度。&lt;/p&gt;
&lt;h2 id=&quot;课堂教学质量&quot;&gt;课堂教学质量&lt;/h2&gt;&lt;p&gt;因为在香港读的MSc，主要是授课为主，所以也简单比较了下大陆和香港两地课堂质量。&lt;/p&gt;
&lt;p&gt;在香港，我修了计算机网络、软件工程等曾经已经学过的水课，也选修了Blockchain、LLM、NLP等相对前言的课程。这些课程给我印象最深刻的地方就是老师的观念很新，这些和计算机相关的课程中，教授们都提到了大模型，并且每一个课程都专门花了1-2节课来阐述LLMs和课程的关联，以及当前LLMs的发展。另外一个发现就是课程中很多知识点都会有附上相关的paper链接供学生深入了解，这个体验是我在大陆读本科时候完全没有过的。本科上课时候从来不知道paper为何物，在香港上课后，才发现原来研究离我如此之近。之前和我本科院校的副教授沟通，他曾经直言不讳的说，自己已经很久不学习了，现在年纪也大了，基本不会去读相关领域的paper。而香港的授课老师基本都在研究一线，所以更能让学生感受到“paper”的概念。&lt;/p&gt;
&lt;p&gt;除此之外，在香港读书的时候，我发现有很多兼职老师。他们没有学校的“编制”，只承担上课和教学义务。这些人一般有PhD学位，同时在社会上还有其他工作。譬如我第一学期的区块链课程老师和第二学期的FinTech课程老师，他们都是在外有工作，在学校以part-time的身份周末上课的。本科时候我们虽然也有企业实践课程，但是因为不算学分或者学分极少，而且课程贼水，所以整体课程质量我相信大家懂得都懂。我觉得香港这项制度真的是一项特别好的创新，对比一直在象牙塔的部分内地老师可能存在的理论脱离实际现象，借鉴这种创新极有可能会缓解目前内地学校和社会需求脱节的问题。&lt;/p&gt;
&lt;p&gt;而且可能是因为港府和香港学校有钱的原因，学校的学习设施真的特别好，图书馆竟然有很多免费的4K显示屏供学生使用。而且同学们还可以在网上直接预约学校的自习室进行小组讨论，遥想自己本科时候小组讨论的时候要么是在露天食堂，要么是找个没人上课的教室，想想还是有点心酸的。&lt;/p&gt;
&lt;h2 id=&quot;入学难度和职业发展&quot;&gt;入学难度和职业发展&lt;/h2&gt;&lt;p&gt;众所周知的一件事是内地因为学生太多，高考尤其卷。所以很多内地家长就会让孩子在高中阶段去香港读书直接申请香港的大学接收本科教育，这确实是一个选择。但是其中要花费的金额和心力绝对不是一个普通家庭能够承担的，笔者对于这块不甚了解，所以就不详谈了。&lt;/p&gt;
&lt;p&gt;另外可能是因为我在港科的原因，学校的创业氛围特别浓厚，很多学生的标签竟然是“连续创业者”，小小年纪竟然多次创业甚至还拿过天使轮，实在是令我十分震惊。在学校的时候也了解了香港的一些博士情况，大家基本都是全奖读博的，每月资助金额大概20,000 HKD，感觉也是博士期间一笔不菲的收入了。&lt;/p&gt;
&lt;p&gt;很多人比较感兴趣MSc的就业和博士升学情况，考虑到本篇内容较多，就放到下篇详谈。&lt;/p&gt;
</content:encoded></item>
<item><title>从在线支付理解数字人民币：清分、结算与资金流</title><link>https://wxxlamp.cn/2025/09/17/what-is-digtal-rmb/</link><guid isPermaLink="true">https://wxxlamp.cn/2025/09/17/what-is-digtal-rmb/</guid><pubDate>Wed, 17 Sep 2025 02:36:00 GMT</pubDate><description>从传统在线支付的信息流与资金流分离说起，厘清交易、清分、结算、交割概念，阐述数字人民币基于央行区块链实现资金流同步的底层逻辑及应用场景。</description><content:encoded>&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&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;h1 id=&quot;传统在线支付方式的本质&quot;&gt;传统在线支付方式的本质&lt;/h1&gt;&lt;blockquote&gt;
&lt;p&gt;传统在线支付是通过数字化纸币完成的交易。其背后还是需要人民币在各个银行流动。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;信息流与资金流&quot;&gt;信息流与资金流&lt;/h2&gt;&lt;p&gt;在线交易没有普及以前，大家都是用人民币直接线下交易的，&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;当我们把现金交给商家时，资金与货物的交割瞬间同步完成，这就是我们常说的“&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;钱货两清&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;”，交易的信息流和资金流也是同步完成的。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/91a95a98_nlark_img1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;但是，手里一直存储现金总是不方便，所以人们的大部分钱都在银行存储。当需要购买商品的时候，去银行取钱，然后再进行线下交易，这确实很麻烦。很自然的，我们就希望通过银行直接在线支付，加快支付的便捷性。如下所示：&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/95ed4685_nlark_img2.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;但解决方案真的有这么简单吗？如果李四的账号在另外一个银行怎么收款呢？假设如下图所示：&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/3777b175_nlark_img3.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;银行A需要真的把张三存的10块钱给银行B，我们成为“资金交割”，资金交割肯定不能用简单的指令完成，所以跨行转账就会非常慢。有没有办法提高跨行转账的速度呢？&lt;/p&gt;
&lt;p&gt;当然有。银行A可以在银行B开一个账户，提现存入一定数量的金额。当张三要求银行A转账10给银行B的时候，银行A只需要给银行B发送转账的指令，银行B自动去A的账户上扣钱即可。这样显然高效了许多。&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;上图的转账方式，最少有两个问题没有解决：&lt;/font&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;多行转账障碍&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;：中国有数百家商业银行，难道商业银行之间都要互相存一笔资金吗？如果一个银行经营破产或者跑路就会酿成重大事故，这是国家绝对不愿意看到的。&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;缺乏监督与约束机制&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;：银行作为商业机构，在缺乏有效监督的情况下，可能存在道德风险（例如擅自将李四的资金转移给张三）。&lt;/font&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;所以，为了解决上述问题，我们需要一个公信力极强的中间机构进行协调。这个角色由中国人民银行（央行）承担。央行规定所有在线支付或转账业务必须通过银联（银行间交易）或网联（涉及支付机构的交易）系统处理，由银联或网联负责资金结算。同时，商业银行和支付机构必须在央行开设存款备付金账户，由央行完成最终的资金交割。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;所以，现代的在线支付系统可以粗略分为两步：&lt;/font&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;信息流&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;：支付机构或银行内部进行记账操作，冻结相应转账资金，完成初步处理。&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;资金流&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;：通过央行清算系统实际划转资金，此时资金才真正完成所有权转移。&lt;/font&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/266325f4_nlark_img4.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;这就是支付常说的信息流和资金流。可以发现，在线支付并没有我们想象的那么简单，整体的流程相当复杂。这么复杂的流程，能秒级完成么？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;显然是不能的&lt;/strong&gt;，因为所有的交易都需要央行受理，如果央行每次交易都需要实时更新各个银行的准备金账户，那么央行系统早崩溃了。为了应对这种情况，央行采用的是T+1批处理的形式，通过轧差的方式处理每天各个银行和支付机构的交易金额。所以，支付的信息流很快就能完成，但是经过央行的资金流可能要等1天才能最终确定。&lt;/p&gt;
&lt;p&gt;那么问题来了，为什么我们普通消费者在用支付宝转账的体感，是“秒级到账的呢”？——这是因为支付宝的信用背书和提前垫资机制。消费者在支付宝进行转账的时候，因为支付宝在合作银行有准备金账户，可以在信息流阶段通知相关银行预划扣资金。所以一般来讲**，当消费者感知交易实时成功，其实只是信息流成功了**，背后资金流需要经过央行结算，其实还没有完成。&lt;/p&gt;
&lt;h2 id=&quot;资金交易、清分、结算和交割&quot;&gt;资金交易、清分、结算和交割&lt;/h2&gt;&lt;p&gt;要理解一次支付行为背后发生的事情，支付（转账）背后&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;具体的信息流和资金流背后究竟在干嘛，我们需要厘清几个核心概念：交易、清分、结算与交割。&lt;/font&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;交易 (Transaction)&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;： 此处的“交易”并非指商品买卖，而是指&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;支付指令的发起&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;。它是一次操作请求，明确了资金的转出方、接收方、金额等信息，是资金流程的起点&lt;/font&gt;&lt;font style=&quot;color:rgb(0, 0, 0);&quot;&gt;。譬如我们在支付宝上点击 “确认支付”，本质是发起了一笔&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0) !important;&quot;&gt;资金转移指令&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0);&quot;&gt;。&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0);&quot;&gt;从用户视角看，支付页面显示 “成功” 代表交易指令已被接收，但这并不意味着资金转移流程的结束&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0);&quot;&gt;。交易的核心参与方包括&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0) !important;&quot;&gt;发起指令的用户&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0);&quot;&gt; ，以及承接指令的支付机构（如支付宝）或银行。&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;清分（Clearing）&lt;/strong&gt;：&lt;font style=&quot;color:rgba(0, 0, 0, 0.85);&quot;&gt;是指&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0) !important;&quot;&gt;一定时间段内的多笔交易集中处理&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85);&quot;&gt;—— 核心是核对交易信息、计算各方的应收应付金额，最终确定支付机构、银行之间的债权债务关系（即 “轧差金额”）。简单说，清分是 “算清楚谁该给谁多少钱”，这个阶段会产生明确的资金支付义务，但尚未实际转移资金。清分的参与方主要是&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0) !important;&quot;&gt;支付清算机构（如网联、银联）、银行或支付机构&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85);&quot;&gt;。&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;结算 (Settlement)&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;： 结算是将清分后的结果进行&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;最终确认&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;，并生成不可撤销的资金划转指令的过程。它标志着资金义务的最终确定。清分与结算常被合称为“清算”，&lt;/font&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85);&quot;&gt;通常包含 “清分（算清楚账）” 和 “结算（付清楚钱）” 两个环节&lt;/font&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;。结算的参与方延伸至&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;中央银行&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;的支付系统。&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交割&lt;/strong&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85);&quot;&gt;（Settlement finality）&lt;/font&gt;&lt;/strong&gt;：&lt;font style=&quot;color:rgba(0, 0, 0, 0.85);&quot;&gt;当结算流程完成、资金转移不可撤销时，就达到了 “交割” 状态。&lt;/font&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;这是资金转移的&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;最终环节&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;，实现了“&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;结算最终性&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;”。中央银行根据结算指令，直接调整相关&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;商业银行&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;在其&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;存款准备金账户&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;中的余额。至此，资金才完成了法律意义上的彻底转移。对于支付机构而言，其存放在央行的&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;客户备付金集中存管账户&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;也会相应地进行调整，而这最终会体现在其托管银行的准备金账户上&lt;/font&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;下面是每个阶段的一般处理时间：&lt;/font&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0) !important;&quot;&gt;阶段&lt;/font&gt;&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(0, 0, 0) !important;&quot;&gt;时间窗口&lt;/font&gt;&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85) !important;&quot;&gt;交易&lt;/font&gt;&lt;/td&gt;
&lt;td&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85) !important;&quot;&gt;7×24 小时（用户可随时发起支付指令）&lt;/font&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85) !important;&quot;&gt;清分&lt;/font&gt;&lt;/td&gt;
&lt;td&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85) !important;&quot;&gt;实时清分（D0）或定时清分（如 T+1 凌晨，具体由支付机构选择）&lt;/font&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85) !important;&quot;&gt;结算&lt;/font&gt;&lt;/td&gt;
&lt;td&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85) !important;&quot;&gt;7×24 小时（小额 / 第三方支付）或工作日 8:30-17:00（大额系统，传统模式）&lt;/font&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85) !important;&quot;&gt;交割&lt;/font&gt;&lt;/td&gt;
&lt;td&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85) !important;&quot;&gt;结算完成后实时交割（账户余额即时更新）&lt;/font&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;在线支付的资金流转&quot;&gt;在线支付的资金流转&lt;/h2&gt;&lt;p&gt;假如说用户A通过支付宝将自己招行账户的100块转移给了用户B的支付宝余额中，我们尝试一句话描述下每个阶段发生的事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;交易 (Transaction)：&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt; A在支付宝点击支付，生成一条支付指令给招行，招行也会冻结A的金额。&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;但是此时用户A就会看到付款成功，同时B也会收到钱，但其实只是支付宝在内部记账了而已，资金并没有真实的流动&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;。&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;清分 (Clearing)：&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt; 支付宝会实时把一段时间内的交易打包发给网联平台，计算出支付宝总共要从招行“收”多少钱，又要给其他银行“付”多少钱（轧差）。&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;结算 (Settlement)：&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt; 网联将算好的净额结果（比如支付宝净收招行100元）生成正式的“要账清单”，发送给央行（中国人民银行）的支付系统。&lt;/font&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;交割 (Settlement Finality)：&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt; 央行大额支付系统根据结算清单，在央行的账本上&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;最终地、不可撤销地&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;完成资金划转：从招行的准备金账户扣减100元，记入支付宝备付金账户。&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;直到这一步，A的100元钱才真正离开招行，进入支付宝的池子。&lt;/font&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;我们可以通过流程图更生动的观察整个在线支付的资金流转：&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/610f57ff_nlark_img5.svg&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;在线支付的缺点&quot;&gt;在线支付的缺点&lt;/h2&gt;&lt;p&gt;传统人民币本质是上现金，在线支付是数字化这些现金的手段。但是普通人是没有办法数字化现金和在线交易的，所以只能通过先把钱存入银行，然后在通过银行或者支付机构这些中间商来完成在线转账交易。&lt;/p&gt;
&lt;p&gt;既然有中间商，就会存在下面的缺点：&lt;/p&gt;
&lt;p&gt;**实时性差：**虽然小额交易对于转账双方来说是实时到账的，但是这个“实时到账”其实只是完成了交易阶段，背后的资金流并没有完成。对于大额交易来说，资金的转账还是需要T+1才能完成（譬如常见的资金提现操作，基本都是T+1到账的）。同时，国内的大额资金交易都要T+1了，那么对于跨国转账来说，可以想象的是一定需要更多时间（譬如USD到PKR的转账可能需要一个星期）&lt;/p&gt;
&lt;p&gt;**手续费高：**目前支付宝和微信提现到卡都是会收取手续费的（0.1%）。对于跨境交易来说，因为涉及的中间行可能更多，平均下来可能会有7%的手续费&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;h1 id=&quot;数字人民币的底层逻辑&quot;&gt;数字人民币的底层逻辑&lt;/h1&gt;&lt;h2 id=&quot;数字人民币的本质&quot;&gt;数字人民币的本质&lt;/h2&gt;&lt;p&gt;不同于传统人民币本质上还是实体现金的特征，数字人民币基于区块链的能力，从创立开始就是数字化的。所以通过数字人民币的在线交易，理论情况下，不需要任何银行或者支付机构的中间商完成。&lt;/p&gt;
&lt;p&gt;举个例子：张三把10元数字人民币给李四，可以通过两个人的电子钱包转账直接完成（就像是张三给了李四10块现金），交易记录直接写在央行运行的区块链上，无需任何支付机构和银行的参与。&lt;/p&gt;
&lt;p&gt;一句话说明：传统人民币的在线交易（网银、支付宝支付等），是信息流先完成，资金流后完成的分离过程。但是数字人民币的在线交易和传统人民币的线下交易一样，信息流和资金流完全统一，同步完成。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/2a1aec14_nlark_img6.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;区块链与智能合约&quot;&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;a href=&quot;https://www.yuque.com/wangxingxing-f4sey/iur8x3/mekvlarxoaw58uvb&quot;&gt;万字长文科普区块链和比特币 · 语雀&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;因为不可修改的特性，当用户的交易信息存放到区块链后，我们就可以相信基于交易信息生成的账户余额的真实性&lt;/p&gt;
&lt;p&gt;所谓的智能合约，可以理解成运行在区块链上的一个合同。同时，智能合约和区块链上的货币是强绑定的。智能合约可以通过编程的形式来约定何时向某人自动转账，一旦智能合约发布到区块链上，便不可更改，同时按照编程约定的信息，在某个条件触发后，向特定的账户转账。&lt;/p&gt;
&lt;h3 id=&quot;数字人民币和比特币&quot;&gt;数字人民币和比特币&lt;/h3&gt;&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;基于区块链和加密算法技术，数字人民币和比特币都属于加密数字货币的范畴，但二者存在本质区别。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;最核心的差异在于：&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;比特币是彻底去中心化的&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;，其运行节点可由任何参与者担任，所有交易信息完全公开透明。而&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;数字人民币采用中心化架构&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;，其区块链节点完全由中国人民银行管控，确保交易记录和账户余额的隐私性与安全性。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;从货币价值角度看：&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;比特币价值波动剧烈&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;：从最初1250枚比特币兑换一个披萨，到单枚价值高达11万美元，其价格具有高度不确定性。&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;数字人民币价值稳定&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;：作为央行数字货币，享有国家信用背书，与纸质人民币等值兑换，具有无限法偿性，价值保持稳定&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;在发行机制方面：&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;比特币发行量固定&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;：创建之初即设定2100万枚的上限，不受任何机构调控。&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;数字人民币发行灵活&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;：与传统人民币一样，由中国人民银行根据经济需求进行宏观调控&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;因此，虽然两者都运用了加密技术，但数字人民币是法定数字货币，代表国家信用；而比特币是去中心化加密货币，更多具有投资属性。这是二者最根本的区别。&lt;/font&gt;&lt;/p&gt;
&lt;h2 id=&quot;数字人民币的应用&quot;&gt;数字人民币的应用&lt;/h2&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=&quot;跨境场景&quot;&gt;跨境场景&lt;/h3&gt;&lt;p&gt;首先，超美股或者港股的朋友可能都知道，国内银行到国外银行的转账是都通swift体系完成的，这背后资金需要经过美元、欧元或者人民币所在央行来进行资金清算。同时还要经过多个中间行进行资金的流转。资金每经过一个银行，就要在一个银行做停留，同时也会被这个银行收取一定比例（或者固定金额）的清算手续费。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/58c18f2d_nlark_img7.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;一般情况下，国际跨行转账到账时效最少T+1，转账手续费抽成一般在5%左右。如果使用数字人民币，排除风控审核等因素，因为不存在任何中间行，所以数字人民币是实时到账的，同时手续费也会非常低。这对于三驾马车之一的跨境来说，绝对是重大利好。&lt;/p&gt;
&lt;p&gt;国家在一带一路场景也在推广数字人民币，摆脱以美国为中心的swift体系。如果一带一路沿途的亚非各国能够接受数字人民币，那么在这些场景下的跨境贸易（尤其是资金流动）将会随时随地进行。&lt;/p&gt;
&lt;h3 id=&quot;B2B领域&quot;&gt;B2B领域&lt;/h3&gt;&lt;p&gt;从商家的角度讲，因为消费者直接用数字人民币支付，直接通过央行区块链转账到商户的数字人民币钱包，所以就不存在中间的支付机构和中间行，也就不会存在“提现”这么一说，与之而来的提现手续费和到账时效慢也就无从谈起。&lt;/p&gt;
&lt;p&gt;除此之外，有了智能合约的加持，相当于把合同和资金流自动绑定，更容易减少赖账行为的产生。举个例子：&lt;/p&gt;
&lt;p&gt;假如A公司和B公司签订一笔采购合同，如约定B公司只要收到A公司的产品后，B公司就给A公司打款100万元。传统的交易方式是合同确立后，有了法律约束。如果B公司收到产品不给A公司打款，就需要启动司法程序来进行追款。&lt;/p&gt;
&lt;p&gt;但是有了数字人民币将完全不一样。基于智能合约，A公司可以和B公司通过代码的方式在智能合约上创建协议。协议约定当有物流公司的消息通知到智能合约后，智能合约就会自动把B公司的数字人民币100万元转移到A公司的账户上。这样就避免了公司赖账行为的产生。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/c22de5e6_nlark_img8.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;不过需要注意的是，商业是复杂的。&lt;u&gt;并不是所有的验收标准都可以数字化，如果甲方拖着不验收，智能合约也就无法被系统触发，甲方账户的数字人民币依然无法结算给乙方&lt;/u&gt;。而这些，也不是单靠技术就可以解决的问题。&lt;/p&gt;
&lt;h3 id=&quot;G2B领域&quot;&gt;G2B领域&lt;/h3&gt;&lt;p&gt;通过智能合约的规则约束，政府的数字人民币财政拨款可以更加定点的被使用，防止传统人民币资金挪用，无法追踪的风险，举个例子：&lt;/p&gt;
&lt;p&gt;国家有一笔扶贫专项，该专项用于购买化肥和种子。首先政府创建补贴的智能合约，同时往智能合约中注入补贴资金。合约规定，1）这些补贴的使用方只限于某些农户，且每个农户都有一定的额度；2）补贴一共一年，到期补贴自动退回政府账户；3）补贴的收款方只能是在政府报备过的农业商家数字人民币的账户&lt;/p&gt;
&lt;p&gt;所以当农户购买完制定农业商家的化肥和种子后，输入在政府报备的商户信息，就可以通知智能合约，使用自己的额度，同时智能合约也会自动的把这些数字人民币转移到商户的数字人民币账户上。&lt;/p&gt;
&lt;p&gt;如果农户没有在该商户买农用产品，而是买了一台Mac，因为智能合约中是没有Apple公司的信息的，所以这些钱也无法打到Apple公司。&lt;/p&gt;
&lt;p&gt;通过智能合约，就能实现补助金的定点使用。这帮助政府更加强力的管控了补助资金，同时也让我们纳税人的钱得到更加高效的利用。&lt;/p&gt;
&lt;h1 id=&quot;数字人民币和稳定币&quot;&gt;数字人民币和稳定币&lt;/h1&gt;&lt;p&gt;随着前几个月美国天才法案的落地和香港《稳定币条例》的正式生效，稳定又一次在市场引起了渲染大波。&lt;/p&gt;
&lt;p&gt;稳定币本质上就是和法定货币挂钩的比特币。关于比特币的文章可以参考：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yuque.com/wangxingxing-f4sey/iur8x3/vhm08q3mw87ocv4p&quot;&gt;七张图告诉你比特币长什么样&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;稳定币作为一种加密货币，继承了如比特币的某些关键特性：&lt;/font&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;**匿名性，**账户是一个hash值，所有的交易是通过hash进行的，即使交易在链上可查，也无法知道hash账户现实的主人是谁&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时性，&lt;/strong&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;转账交易可在分钟级内完成，7x24小时运作，打破了传统金融体系在时间和地域上的壁垒，无需等待数日的清算周期。&lt;/font&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;同时，稳定币和法定货币（如美元和港币）是一比一挂钩的，即有人买了一块钱稳定币，那么区块链上就会有一枚稳定币产生。&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;合规的稳定币发行方需持有等值的法定货币储备，以确保其偿付能力&lt;/font&gt;。&lt;/p&gt;
&lt;p&gt;因为市值不会剧烈波动，加上稳定币去中心化、匿名化和实时结算的特性，引起了很多人的追捧。&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(15, 17, 21);&quot;&gt;从全球视角看，稳定币已成为地缘金融的新舞台：&lt;/font&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于美国来说，天才法案使得稳定币（和美元1比1兑换）得到监管，也就是说明美国开始认可稳定币，这样基于美元的稳定币的交易量就会迎来爆炸性的增长。因为匿名化的特性，甚至包括之前被美国制裁的国家（如俄罗斯等）也可以使用美元基准的稳定币重新和其他国家进行贸易，这无疑又增加了美元的霸权地位。&lt;/li&gt;
&lt;li&gt;对于香港来说，是中国资金流动的出口。一般大陆的贸易资金无论是出口还是进口都会经过香港。所以香港认可稳定币会进一步巩固香港港口城市的地位。通过使用基于港币的稳定币进行交易，将会大大增加跨境贸易资金的流动性，降低交易成本。&lt;/li&gt;
&lt;li&gt;对于中国大陆来说，因为数字人民币已经有了稳定币大部分功能（如实时结算），且数字人民币具有可编程性、央行背书等特性，中国大陆大概率不会推行稳定币。而且由于中国外汇管制的特性，也不允许匿名化的美元、港元稳定币在大陆内流通。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，一句话来讲，稳定币是机会，但是在大洋彼岸。在大陆，不妨多看看数字人民币。&lt;/p&gt;
</content:encoded></item>
<item><title>比特币与区块链基础：密码学、共识与交易</title><link>https://wxxlamp.cn/2025/09/07/what-is-bitcion-and-blockchain/</link><guid isPermaLink="true">https://wxxlamp.cn/2025/09/07/what-is-bitcion-and-blockchain/</guid><pubDate>Sun, 07 Sep 2025 02:36:00 GMT</pubDate><description>系统科普区块链与比特币核心原理，从中心化与去中心化货币对比出发，讲解区块链链式结构、Hash指针、非对称加密、UTXO机制及PoW共识，梳理比特币交易全流程。</description><content:encoded>&lt;p&gt;本文面向技术和小白用户，从去中心化、区块链，再到比特币的原理，从科普和问题的视角，尽可能帮助大家弄懂去中心货币的本质和技术原理。&lt;/p&gt;
&lt;h1 id=&quot;中心化和去中心化&quot;&gt;中心化和去中心化&lt;/h1&gt;&lt;p&gt;从概念上理解，所谓中心化和去中心化，代表着数据存储和逻辑执行的两种方式。中心化，即数据（如资产记录）在某一个地方或者机构存放，且逻辑执行（如交易）由某个单一的系统独立完成；而去中心化，也叫分布式（decentralization），即数据（如资产信息）在多个对等的节点存放，且执行逻辑（如交易）必须被所有的节点认可才算有效。&lt;/p&gt;
&lt;h2 id=&quot;中心化货币的优劣&quot;&gt;中心化货币的优劣&lt;/h2&gt;&lt;p&gt;在区块链出现以前，几乎所有的系统都是中心化的（注意，虽然有分布式系统，但是这个分布式系统一般也是由单一的组织管控，无法随意扩展到任何一个组织或个人，所以诸如OceanBase这样的分布式数据库也是中心化的）。&lt;/p&gt;
&lt;p&gt;典型的中心化应用是中心化货币，它们基于国家信用，是由各国央行发行的衡量财富以及交易的凭证，具有无限法偿性。中心化货币具有两个优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;稳定性&lt;/strong&gt;。拿美联储发行的美元举例，美元的价值受到美联储调控，价值相对稳定，&lt;strong&gt;不会出现大幅的波动&lt;/strong&gt;，人们通过美元进行交易，货物的价值会一直稳定在一个范围内。同时，中心化货币由国家信用背书，只要国家稳定，货币就有了流通的可能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;抗风险&lt;/strong&gt;。如果货币因为各种意外交易被盗窃，基于国家信用的中心化货币可以通过追缴等各种形式，帮助用户追回丢失金额。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是，中心化有一个很大的问题就是容易单点故障，如果单点的央行歇菜，那么基于国家信用的中心化货币也就成为了无源之水，持有人的货币就会直接清零，与之对应的财富也将同样烟消云散。除此之外，中心化货币的劣势还包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单点控制&lt;/strong&gt;。因为货币受到中心化的央行管控，所以人们持有的这些货币将随时随地都有可能被&lt;strong&gt;央行冻结&lt;/strong&gt;。譬如俄罗斯被美国制裁后，俄罗斯相关公司在美国及其关联国家储备的美元被毫无理由的无限期冻结&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;央行失信&lt;/strong&gt;。货币的发行是由央行自主决定的，如果央行太挫，错误的决策导致&lt;strong&gt;货币超发&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;。中心化货币在基于swift协议的跨境交易的中，效率尤其底下。这是因为不同国家有不同的央行，每个央行都会发行自己的货币，不同主体的中心化货币在一次交易中，会通过多家跨国银行进行清算。因为参与者变多，会导致跨境汇款的效率奇低，手续费奇高。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;去中心化货币的机遇与挑战&quot;&gt;去中心化货币的机遇与挑战&lt;/h2&gt;&lt;p&gt;中心化货币对于政府信用极高的国家，其实是满足99%人的需求的。但是世界上总有另外1%的人，不满足于现状，希望通过去中心化的思路，来解决中心化货币的各种弊端。&lt;/p&gt;
&lt;p&gt;所谓去中心化货币，就是货币的发行和交易，都不是由单一的主体一家独断的，而是由使用这个货币的所有节点来共同决定。在去中心化货币中，没有央行，但所有人都是央行。如果货币的发行和交易需要得到社区多数节点的同意，那么，最少解决了下面几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;货币可以在全球所有的节点上运行，即使某一个节点宕机，仍然不会影响货币的基本运行&lt;/li&gt;
&lt;li&gt;货币的发行不再单一机构或者组织控制，减少了单一主体滥发的风险。货币的发行量必须要社区大多数节点同意，每个人都有参与权&lt;/li&gt;
&lt;li&gt;货币的交易不再受到单一机构管控，在没有社区多数节点的同意下，没有任何的交易会被拒绝或者冻结&lt;/li&gt;
&lt;li&gt;货币交易在全球所有节点上运行，本国交易和跨国交易本质一样，跨境支付效率会比目前的swift体系更高。（注意，非跨境场景下，中心化货币的效率更高）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从上面解决的问题来看，对于大多数人来讲，去中心化的货币交易，未免太“自由”了。央行无法管控，这对于任何一个主权国家是无法接受的。无法管控货币发行，意味着如果有重大性经济危机，无法通过增发或者减发货币来调整市场经济（如08年金融危机）；无法管控货币交易，意味着大量的灰黑产交易更加猖獗。&lt;/p&gt;
&lt;p&gt;同时，因为是去中心化的货币，所以就意味着所有的交易数据必须要要在社区每一个全节点中存储。随着交易的膨胀，数据量越来越多，去中心化货币对社区成员的存储和计算要求也会越来越高。&lt;/p&gt;
&lt;h1 id=&quot;区块链是什么&quot;&gt;区块链是什么&lt;/h1&gt;&lt;p&gt;区块链是实现去中心化货币的一种方式。同时，结合非对称加密等加密算法，就可以实现出来去中心化的加密货币。所以这里有一些误区需要澄清，即：加密货币不一定是去中心的，去中心的，也不一定是加密货币。譬如，黄金就是典型的去中心化的、非加密货币。不过，对于现代的数字货币来说，如果不加密就可能导致A冒充B的情况，所以像我们常说的比特币、以特朗普为 IP 的迷因币等既是加密货币，又是去中心化货币。&lt;/p&gt;
&lt;p&gt;区块链是由一个一个区块组成，每个区块分为两部分，一部分是区块头，记录一些基本信息，另一部分是区块体，主要记录一些交易的数据。这些交易分为两种，一种是铸币交易，即凭空发行了一些货币，并且把该货币转账给某个账户；另一种是普通转账交易，即账户A把钱转给了账户B。要注意的是，社区中每个全节点都会维护一整条区块链。&lt;/p&gt;
&lt;h2 id=&quot;从宏观上看区块链&quot;&gt;从宏观上看区块链&lt;/h2&gt;&lt;p&gt;从技术的角度讲，区块链本质也是一个链表。传统的链表一般是上一个区块会通过指针下一个区块，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/715e78c0_btc_img_01.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;不过，区块链特殊的地方在于，区块链链中的下一个区块会指向上一个区块。而且它不是通过传统的指针地址指向上一个区块，而是通过Hash指针。&lt;/p&gt;
&lt;p&gt;所谓的hash指针，其实本质上就是一个hash值。有计算机基础的朋友都知道，hash算法即是将某个值X，映射成另一个值Y，且这个过程是不可逆。常见的hash算法，如SHA-256，会将一个无轮多大的X，映射成一个固定长度256位的新值Y&lt;/p&gt;
&lt;p&gt;区块链中的下一个区块b指向上一个区块a，本质就是区块b，记录了区块a的hash值。如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/ab2a1c74_btc_img_02.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;这样做的一个好处就是，如果a区块的内容发生改变，b区块记录的hash值就会发生改变，那么b区块就会认为a区块非法，整个链就会断掉。要想链不断掉，保存该区块链的节点不仅需要修改区块 a，还需重新计算区块 a 之后所有区块的哈希值（因为每个区块的哈希都依赖前一个区块）—— 这个过程需要消耗大量算力（如 PoW 机制下需重新完成‘挖矿’验证），技术成本极高。更关键的是，即使完成修改，该节点还需控制全网超过 51% 的算力（PoW）或质押资产（PoS），才能迫使其他节点放弃原有的合法链条、接受篡改后的版本。这种51% 攻击的成本远超篡改能获得的收益，因此基本杜绝了篡改可能。&lt;/p&gt;
&lt;p&gt;所以，通过区块链的这个特性，保证了链上的每一个区块都是&lt;strong&gt;不可篡改&lt;/strong&gt;的。其实这个就是区块链的全部内容，没有任何复杂的东西在，一句话来说，&lt;strong&gt;区块链&lt;/strong&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85);&quot;&gt;是一个由去中心化节点共同维护的不可变数据库&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.85);&quot;&gt;（这种“不可变”并非绝对，理论上需无限算力才能篡改，但在现实中，足以保证链上交易记录的真实性和安全性）&lt;/font&gt;&lt;/p&gt;
&lt;h2 id=&quot;去中心化货币为什么需要区块链&quot;&gt;去中心化货币为什么需要区块链&lt;/h2&gt;&lt;p&gt;在聊区块链之前，我们先思考为什么去中心化货币需要区块链。又因为区块链的一大特性就是不变性，所以，话题就变成了：为什么去中心化货币需要不可变。&lt;/p&gt;
&lt;p&gt;中心化货币的信任来自央行背书：比如人们相信央行不会赖账、银行会如实记录存款，哪怕银行系统数据被篡改，央行也能通过权威力量追溯修正。&lt;br&gt;但去中心化货币没有任何中央权威，每一个节点的权利都是一样的。它的 “信任” 只能来自技术层面的 “数据真实性” —— 如果区块中的交易记录可以被随意篡改（比如A转出去的 100 元，能偷偷改回自己账户；或者A伪造一笔交易转走B的钱），整个货币体系的 “价值锚点” 会瞬间崩塌。&lt;/p&gt;
&lt;p&gt;所以，如果去中心化的交易从技术上讲是不可变的，那么存在于区块中的数据就是100%可以信任的（注意：这种信任不是基于中心化货币对央行的信任，而是基于技术上不可修改的信任）&lt;/p&gt;
&lt;h2 id=&quot;比特币、区块链、以太坊的关系&quot;&gt;比特币、区块链、以太坊的关系&lt;/h2&gt;&lt;p&gt;相信很多人或多或少都听说过比特币、区块链和以太坊，但是究竟他们有什么其他区别呢？&lt;/p&gt;
&lt;p&gt;简单来讲，比特币和以太坊都是基于区块链的、具有去中心化货币的属性。不过以太坊虽然有价值存储和交易的货币属性，但是更重要是，以太坊支持Smart Contract。&lt;/p&gt;
&lt;p&gt;以太坊的存在，使得基于Smart Contract的App变成了分布式的。DApp的核心逻辑（如交易规则、资产确权）可以通过Smart Contract可以被打包在区块上，进而flooding sync到全球所有的以太坊节点中（前端资源仍然可以部署到中心化机器中）。这种操作使得应用不再像Web2时代一样存在于某一家机构的单点服务器中，受到App厂商的绝对控制，而是存在于所有的节点机器，不可变的Smart Contract使得即使是App的厂商也没有中途修改、操作数据的权利——这就是Web3。&lt;/p&gt;
&lt;h1 id=&quot;基于区块链账本的比特币&quot;&gt;基于区块链账本的比特币&lt;/h1&gt;&lt;p&gt;区块链保证了数据的不可篡改性，在这种特性的基础上，比特币作为货币，运行比特币的节点还要具备货币发行、支持合法交易、拒绝非法交易（如A账上只有10块，却要给B转账100块）的能力。&lt;/p&gt;
&lt;p&gt;但是，全球有数以万计的比特币节点，这些节点的权利是相同的，本节主要探究比特币通过区块链以及其他特殊的数据结构来保障交易和区块的合法性。&lt;/p&gt;
&lt;h2 id=&quot;非对称加密&quot;&gt;非对称加密&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;前文已经说过，比特币是一种加密货币。所以，我们首先了解下比特币涉及到的部分加密算法。我们之前已经简单聊过Hash算法，所以，本章简单讲述下非对称加密算法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;区块链的每个区块中都会记录一定数量的转账交易。假如说区块上其中一笔交易是Alice将10BTC转给Bob，从密码学的角度来讲，我们需要做哪些加密工作呢？&lt;/p&gt;
&lt;p&gt;考虑如果Alice通过支付宝转账给Bob的场景，在扫码支付前，Alice需要输入密码验证当前支付宝账户确实是她自己的。这其中有两个步骤，第一个步骤是转账前需要在中心化的支付宝中设置密码，第二步是转账中输入密码被中心化的支付宝验证通过。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/27e44fd0_btc_img_03.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;同样的，在区块链上的一笔交易，发起交易的人也需要证明她就是Alice。所以，第一步，Alice应该先对这笔交易设置密码，在区块链中，我们把设置密码这个动作叫做签名（信用卡的账单也叫签名）。当这笔交易准备被打包到区块中的时候，区块进行第二步操作，即验证签名的真实性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/23123fe5_btc_img_04.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;因为比特币是去中心化的，每个节点不可能存储账户的密码（节点存储账户的密码，所有账户的密码都会泄露）。在没有密码情况下的签名和验证，就可以通过非对称加密在不泄露密码的问题来验证用户。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;非对称加密分为公钥和私钥，计算机相关专业的朋友们应该都知道，HTTPS就是基于非对称加密的。简要的流程是：1）client将server的公钥作为key对内容进行非对称加密后，传输给server；2）server使用自己的私钥对内容进行非对称解密。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从HTTPS的流程中，我们可以发现，非对称加密的核心是“公钥（公开）与私钥（保密）一一对应”，分两种核心场景：1. 加密场景：公钥加密数据 → 仅私钥能解密（如 HTTPS 传输）；2. 签名场景：私钥对数据（如交易信息）加密生成“签名” ——任何人可用公钥解密签名，验证数据是否来自私钥持有者。这两种场景共同支撑了比特币的交易安全性。&lt;/p&gt;
&lt;h2 id=&quot;比特币账户&quot;&gt;比特币账户&lt;/h2&gt;&lt;p&gt;基于非对称加密的特性，在Alice给Bob转账的交易中，Alice除了1）写下交易信息之外；还要2）通过私钥对自己的交易签名；3）写下自己的公钥信息，供节点打包到区块时对交易的签名进行验证。&lt;/p&gt;
&lt;p&gt;所以，我们可以很容易的得出来如何唯一定位比特币中的一个账户，即比特币中的账户号码（类似于银行卡的账号和密码）是一个符&lt;strong&gt;合非对称加密的公私钥对&lt;/strong&gt;。公钥对外公布，用于收款和展示自己的身份。私钥由用户自己保管，用于交易的签名验证。从上面一段可以看出来，如果私钥，被别人窃取，那么自己账户的钱就会很容易被被人转走。因为不是中心化，所以即使钱被别人转走，也不可能有机构帮失主把钱追缴回来的。&lt;/p&gt;
&lt;p&gt;那么问题来了，我们是如何知道比特币某个账户的余额呢？&lt;/p&gt;
&lt;p&gt;在中心化的银行系统中，可以通过关系型数据库（Oracle，MySQL）等来存储账户和余额的映射关系。一旦用户发生交易，中心化系统只需要执行简单的update语句更新数据即可。但是，在去中心化的系统中，怎么保证每个节点都去更新区块中交易影响到的账户呢？又怎么保证每个节点的账户数据都是一致的呢？&lt;/p&gt;
&lt;p&gt;这其实是很难的，而且我们知道，基于区块链不可变的特性，数据一旦插入，就不能更新，所以在比特币的区块链中，账户的余额是不能更新的，那该怎么表示呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/c5e69815_btc_img_05.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;如上图所示，我们可以使用交易账本计算，因为区块链会记录每一笔交易的信息（包含BTC转出方和接受方），通过对交易流水的数据进行累加，即可得出账户的余额——这就是本节的标题，为什么比特币是基于区块链&lt;strong&gt;账本&lt;/strong&gt;的加密货币。（以太坊是基于账户的，通过MPT设计更加复杂，本篇不表）&lt;/p&gt;
&lt;h2 id=&quot;区块链、节点、账户&quot;&gt;区块链、节点、账户&lt;/h2&gt;&lt;p&gt;上一节中，我们引入了比特币账户的概念。初学者可能会有点混淆，是否发起交易的账户是否和节点是等价的？每个节点必须要维护一个区块链么？&lt;/p&gt;
&lt;p&gt;必须要澄清的一点是，账户和节点没有必然关系。节点是用来运行区块链的机器，每个节点都是等价的，运行着一个相同的区块链。区块链中的区块打包的是账户之间交易的信息。一个没有任何账户的人，也可以运行一个区块链节点（只不过没有任何收益，所以不会有人这么干）&lt;/p&gt;
&lt;p&gt;举个例子，Alice有一个账户，账户包含公钥PublicA和私钥PrivateA。Alice拥有一台包含整条区块链的机器，这个机器就是比特币中的一个节点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/9beb844a_btc_img_06.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里有一个问题：随着区块链中交易的增多，整条链需要的存储空间也就越大。如果每个节点都存储整条链的所有信息的话，意味着每个节点都需要有一个超大存储空间的机器，这样比特币的使用门槛就会变得非常高。所以，为了让手机也可以成为一个区块链节点（人人可用），比特币协议把节点分为全节点和轻节点。&lt;/p&gt;
&lt;p&gt;全节点存储区块链所有的信息，负责验证所有交易和区块的合法性。其中，部分全节点会参与打包区块（即挖矿），这类参与挖矿的全节点被称为“矿工”。轻节点不存储交易的明细，只存储区块链中每个区块头，轻节点只能从全节点中同步合法区块，不负责交易的写入。&lt;/p&gt;
&lt;h1 id=&quot;比特币的数据结构&quot;&gt;比特币的数据结构&lt;/h1&gt;&lt;h2 id=&quot;单个区块的数据结构&quot;&gt;单个区块的数据结构&lt;/h2&gt;&lt;p&gt;现在我们了解到，轻节点只有区块链中的区块head，全节点既有区块head存储metadata，也有区块body存储所有的交易信息。如下所示（具体的区块内容可以访问&lt;a href=&quot;https://www.oklink.com/zh-hans/bitcoin/block/914370&quot;&gt;oklink&lt;/a&gt;查询最近真实的区块）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/f67db3e0_btc_img_07.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里我们需要强调一些更细节的东西，之前讲到的hash指针，其实不是对上一个区块的所有数据取hash。假如说区块b的上一个区块是区块a，那么区块b获取区块a的hash值流程应该是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;区块 a 生成时，先将自身所有交易通过 Merkle Tree（下文会讲）计算出所有交易的最终 Hash 结果（Merkle root），并将其写入自己的区块头&lt;/li&gt;
&lt;li&gt;区块 a 再对自身的完整区块头（包含交易hash、前一区块 Hash 等）计算 Hash，得到“区块 a 的区块头 Hash”&lt;/li&gt;
&lt;li&gt;区块 b 生成时，将“区块 a 的区块头 Hash”直接写入自己的区块头，形成链式关联&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;区块中交易的数据结构&quot;&gt;区块中交易的数据结构&lt;/h2&gt;&lt;p&gt;那么除了基于区块链之外，比特币是否还有其他特殊的数据结构？&lt;/p&gt;
&lt;p&gt;答案是肯定的。假如说Alice给Bob转了10BTC，Bob是怎么知道钱已经转到自己的账户里面了呢？在中心化货币场景中，Bob可以去银行查询。在BTC这种去中心化场景中，如果Bob拥有一个全节点，则可以遍历所有节点检查是否有一笔“Alice给Bob转了10BTC”的交易。但是如果Bob拥有的是一个轻节点呢？（轻节点只有每个区块的head，没有任何的交易信息）&lt;/p&gt;
&lt;p&gt;此时，Bob的轻节点就只能向P2P网络中的任意一个全节点求证，是否有一笔“Alice给Bob转了10BTC”的交易。但是，因为比特币是去中心化货币，所以理论上任何一个节点都是不可信的，假如Bob的轻节点请求到了Alice的全节点，Alice的全节点直接回复Bob有这笔交易（但其实没有），那么Bob就约等于被欺骗了。&lt;/p&gt;
&lt;p&gt;如何解决这个问题呢？——通过Merkle Tree来维护整个交易的结构。&lt;/p&gt;
&lt;h3 id=&quot;Merkle-Tree&quot;&gt;Merkle Tree&lt;/h3&gt;&lt;p&gt;Merkle Tree是指所有的交易通过两两取hash，再对hash值两两取hash，最后一直递归，获取一个根hash值（叫做Merkle根）。所有的交易，和这个过程中产生的hash，就会构成一颗Merkle Tree，如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/5f5ac865_btc_img_08.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从图中我们可以发现，如果要修改某个交易的hash，随着修改传递，Merkle根也会随之改变，因为Merkle根被包含在区块header中，所以Merkle根的修改会导致区块头的hash指针修改，进而影响到所有的后继节点，所以这几乎是一个不可能完成的事情。&lt;/p&gt;
&lt;p&gt;Merkle Tree有一个特性，即给出部分Hash值（即Merkle路径），就可以验证某一笔交易是否存在。基于上图我们可以举个例子，如果一个轻节点要验证TX1交易是否存在，全节点只需要给出TX2、H(2)和H(34)的hash值，轻节点就可以计算出Merkle根hash，然后将这个hash与轻节点自己区块头的hash做对比，如果相等，则说明TX1这个交易确实存在。&lt;/p&gt;
&lt;p&gt;从实际出发，如果Bob的轻节点希望验证交易TX是否已经被打包到全节点中，此时验证流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bob的轻节点向P2P网络中的任意一个全节点请求验证交易TX。然后&lt;/li&gt;
&lt;li&gt;这个节点会给Bob返回TX交易的Merkle路径。然后&lt;/li&gt;
&lt;li&gt;Bob 的轻节点用“TX 的 Hash + Merkle 路径”自行计算出“默克尔根”，&lt;strong&gt;并与自身区块头中存储的默克尔根对比&lt;/strong&gt; —— 若一致，说明 TX 确实存在于该区块中；若不一致，则全节点返回的路径无效&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因为轻节点只是不会维护交易信息，他们仍然会保存区块链上的每个区块头，区块头中包含Merkle根。轻节点校验的本质就是用自己区块中正确的Merkle根和通过（交易+Merkle路径）计算出来的Merkle根作比较。&lt;/p&gt;
&lt;h2 id=&quot;比特币数据结构概览&quot;&gt;比特币数据结构概览&lt;/h2&gt;&lt;p&gt;比特币中，每一个全节点究竟是什么样的？它们是如何组织各个区块的？Merkle Tree究竟会被存储到哪里？&lt;/p&gt;
&lt;p&gt;一张图来揭露每个全节点中的数据结构：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/424a115c_btc_img_09.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;（注意，每个全节点的区块链中的区块数据都是一致的）&lt;/p&gt;
&lt;h1 id=&quot;比特币的共识机制&quot;&gt;比特币的共识机制&lt;/h1&gt;&lt;p&gt;在分析比特币的交易详情之前，我们首先要思考比特币作为去中心化货币的元老级货币，它如何1）让每个节点达成共识、保证每个节点的区块链数据是一致的？2）防止恶意节点将非法交易散播到其他正常的节点中？&lt;/p&gt;
&lt;p&gt;这些技术问题我们需要通过交易指针、UTXO以及多节点的共识协议来解决。&lt;/p&gt;
&lt;h2 id=&quot;工作量证明（PoW）&quot;&gt;工作量证明（PoW）&lt;/h2&gt;&lt;p&gt;因为比特币是去中心化货币，每个节点都维护着一个区块链。前文中提到，每个节点区块链中所有的区块以及区块中的交易数据都应该是一致的。但是每个节点都是独立的，所以比特币就必须要&lt;strong&gt;通过一个机制来让所有节点认可某一个节点中区块的数据&lt;/strong&gt;（即当某一个节点A的下一个区块形成后，其他所有节点都要follow这个决策，并且放弃打包自己的区块，将节点A的这个区块作为自己区块链中的下一个区块）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/8e367f8b_btc_img_10.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当交易很多的时候，可能A节点打包了a,b,c三个交易在自己的区块，同时B节点也打包了b,d,f三个交易在自己的区块，这种情况下，其他节点以A、B两个区块的哪个区块为准呢？在比特币中，工作量证明就是为了解决某个节点新打包的区块，被其他所有节点认可的问题。&lt;/p&gt;
&lt;p&gt;在比特币的世界中，程序在节点新生成区块的时候，会设置一个难度值X，每个节点打包新区块时，需对“区块头”（包含前一区块哈希、默克尔根、时间戳、难度目标、nonce 等）计算哈希，得到的哈希值Y需满足难度 X 的具体要求。由于哈希值不可预测，节点只能通过不断调整 nonce等方式，反复尝试计算，直到得到符合难度X的哈希值Y。 当Y符合X的要求的时候，节点打包的新区块才会被认为是合法区块，广播出去后才有可能被其他节点接收——&lt;strong&gt;这种通过尝试不同nonce计算Y以符合X的证明过程就叫做工作量证明（PoW）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比特币系统每经历2016个区块会根据所有节点的算力动态调整难度值X，以保证节点打包区块的平均时间在10分钟左右，即每次交易被发布到区块链上大概需要十分钟（确认交易需要等待后续6个区块生成，大概一小时），这与支付宝动辄十万级别的TPS根本无法比较。节点们计算Y来打包区块的过程就被称作是挖矿，这些挖矿的节点，也被叫做矿工。&lt;/p&gt;
&lt;h3 id=&quot;为什么要基于PoW&quot;&gt;为什么要基于PoW&lt;/h3&gt;&lt;p&gt;可能有人会问，所有矿工通过PoW这种无效计算的方法，来抢夺发布区块的权利，简直就是白白浪费算力和电力，就算是雅鲁藏布江水坝建好可能也经不起这么造。&lt;/p&gt;
&lt;p&gt;为什么不使用时间呢？谁发布区块早就听谁的不行么？&lt;/p&gt;
&lt;p&gt;当然不行，原因有二：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个节点可以理解成一个独立的计算机，单靠本地时钟的话，不同计算机的时间是可能有偏差的（甚至某个节点可以估计把自己的本地时钟调早）。这就会导致不同节点理解的最新时间不一样。&lt;/li&gt;
&lt;li&gt;如果基于时间打包区块，同一时间段会有大量不同的节点被发布到网络上，这样会造成区块链的大量分叉，但是&lt;strong&gt;我们只能以最长的那一条为合法链&lt;/strong&gt;，这样就会导致发布的大量区块其实是无效的，白白浪费算力，影响比特币社区。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/23b7f548_btc_img_11.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当然，随着区块链社区的发展，以太坊已经发展出了不需要浪费算力的投票机制（权益证明PoS），本文主要讲比特币，所以PoS也暂且不表。&lt;/p&gt;
&lt;h3 id=&quot;矿工为什么要挖矿&quot;&gt;矿工为什么要挖矿&lt;/h3&gt;&lt;p&gt;矿工为什么要浪费大量的算力去挖矿呢？难道是geek心爆棚，无偿帮助社区所有的交易打包到区块？显然不是。为了维护社区的繁荣，当矿工打包交易到区块中，系统会奖励第一个计算出符合难度要求的nonce的矿工，不只是允许这个矿工把交易打包成区块发布到区块链上，更重要的是，还会给这些矿工发放BTC奖励。&lt;/p&gt;
&lt;p&gt;这种奖励和转账交易不一样，它没有输入，&lt;strong&gt;而是“凭空”生成新的比特币&lt;/strong&gt;——这也就是我们常说的&lt;strong&gt;铸币交易&lt;/strong&gt;。通过这种铸币交易，比特币也就具备了货币发行的特性。&lt;/p&gt;
&lt;p&gt;比特币平均每 10 分钟发放一次区块奖励，每 4 年（210,000 个区块）奖励减半一次，目前奖励为 3.125 BTC/区块，按照现在比特币的&lt;a href=&quot;https://www.oklink.com/zh-hans/bitcoin&quot;&gt;价格&lt;/a&gt;（11万 USD/枚），发布一次区块，矿工可以获得36万 USD，这是一个普通人一辈子都赚不到的钱。&lt;/p&gt;
&lt;h2 id=&quot;交易合法&quot;&gt;交易合法&lt;/h2&gt;&lt;p&gt;之前我们提到过，在一次交易中，假如Alice给Bob转账 10 BTC，Alice必须要对这次交易使用私钥签名。当其他节点使用Alice的公钥对该交易进行验证，如果符合预期才说明交易合法。&lt;/p&gt;
&lt;p&gt;但是，因为比特币是基于账本，没有账户的概念，如果我们只是通过非对称加密验证了交易发起人的真实性，却没有验证交易发起方余额是否充足。此时有两种情况需要注意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;u&gt;假如Alice之前因为挖矿获得了 10 BTC的出块奖励，它要转给Bob 20 BTC，区块如何拒绝该交易？&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;假如Alice之前因为挖矿获得了 10 BTC的出块奖励，之前已经转给了Bob 10 BTC，过了一段时候后又转给了Bob 10 BTC，区块该如何拒&lt;/u&gt;绝？&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;交易指针&quot;&gt;交易指针&lt;/h3&gt;&lt;p&gt;办法很简单，比特币要求，转账人在发布交易的时候，需要通过hash指针的方法，将交易指向发送者获之前获得比特币的交易。如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/22a079e5_btc_img_12.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;这样一来，打包该交易的节点就可以通过hash指针轻松的找到Alice获取BTC的那一笔交易用来验证。“Alice转账20BTC给Bob，但是Alice之前只获得了10BTC”，有问题，验证失败，问题一解决。&lt;/p&gt;
&lt;p&gt;但是，问题二还没解决。假如TX1完成后，Alice紧接着创建了一笔TX2，又给Bob转了10BTC（如下所示），这种情况单靠交易指针是验证不出来这种“双花交易”的。这种时候，我们就需要在节点中维护一个状态，记录每一笔交易输出的BTC是否被花出。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/fd9c71b0_btc_img_13.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;UTXO&quot;&gt;UTXO&lt;/h3&gt;&lt;p&gt;这种记录交易输出是否被花出的数据结构叫做Unspend Transaction Output（UTXO）。&lt;/p&gt;
&lt;p&gt;UTXO可以理解成是以交易和收款人为key的map，value是当前交易的输出是否被其他交易花掉。以上图举例，TX0给了Alice 10 BTC的出块奖励，那么包含TX0区块的UTXO就是&lt;code&gt;{&apos;TX0:Alice&apos;: false}&lt;/code&gt;，代表TX0交易的输出未被花掉。当TX1被打包到区块中后，那么包含TX0区块的UTXO就会变成&lt;code&gt;{&apos;TX0:Alice&apos;: true}&lt;/code&gt;，代表TX0交易的输出已经被花掉。之后，如果TX2再指向TX0，节点验证时发现TX0的输出已经被花掉了，就会判断TX2是笔非法交易，不会把TX2打包到区块中。&lt;/p&gt;
&lt;p&gt;与区块链不可变性质不同的是，这个UTXO是可以被改变的，它不属于区块链的一部分（也不参与hash的计算）。每个节点&lt;strong&gt;不会&lt;/strong&gt;通过网络把自己区块的UTXO同步给其他节点，而是需要节点在更新区块的时候自行本地构建UTXO。随着新增区块的增加，UTXO会根据区块链中交易的情况进行动态改变。&lt;/p&gt;
&lt;p&gt;UTXO的产生是为了帮助节点更快的验证交易的合法性。假如没有UTXO，节点为了防止“双花交易”，就需要从后往前遍历每个区块的每个交易，按照现在91万个区块数量来算，遍历一次也是一笔不小的计算开销。&lt;/p&gt;
&lt;h2 id=&quot;区块合法&quot;&gt;区块合法&lt;/h2&gt;&lt;p&gt;通过交易指针和UTXO，使得区块在打包交易的时候，能够精准地识别出交易是否正确。如果交易正确，节点才会把交易打包到区块中。如果有一个异常节点打包的区块中包含一笔不正确的交易，网络中的其他节点是不会认可的。&lt;/p&gt;
&lt;p&gt;那么，是否区块中所有的交易正确，节点打包的区块就会被网络上的其他节点接收呢？当然不是，交易合法只是区块的一个必要条件，验证区块合法，还需要之前提到的PoW。&lt;/p&gt;
&lt;h3 id=&quot;如何验证区块合法&quot;&gt;如何验证区块合法&lt;/h3&gt;&lt;p&gt;当节点把区块发布到网络上后，其他节点会对这个区块进行PoW验证（即对区块头的nonce，pre hash，Merkle root等计算hash值，判断是否符合挖矿难度）。如果工作量证明符合要求，会对该新区块的每个交易通过交易指针和UTXO进行验证，当区块中的所有交易都合法的时候，其他节点才会承认该区块是合法的。&lt;/p&gt;
&lt;p&gt;但是，区块合法不代表其他节点一定会接收这个新区块。&lt;/p&gt;
&lt;p&gt;考虑一种情况，节点新产生了一个合法区块A（区块中的交易也是合法的），但是这个合法区块A中的pre hash是一个几年前的区块hash（如下所示），这个区块会被其他节点接收么？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/72ca1d6d_btc_img_14.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;答案是不会的，比特币中的区块链可能会有多个分叉，因为无法保证每个分叉中交易的不同，所以不可能多个分支都是有效的。在比特币中只有唯一一条区块链是有效的，它就是最长（累计工作量最大）的那条。所以当一个指向很久以前的区块A被发布到网络中的时候，其他节点会把该区块在区块链中的工作难度和自己本地链的工作难度之和做比较，如果没自己链难的话，区块A是会被无情抛弃的，这样区块A中的交易也无法被比特币网络达成共识，产生区块A的节点也就白挖矿了。&lt;/p&gt;
&lt;p&gt;所以要避免这种情况，就是每个矿工在发现网络中有比自己长的区块链时，立刻修改打包区块的pre hash，重新计算nonce等参数完成PoW。&lt;/p&gt;
&lt;h3 id=&quot;同时产生合法区块怎么办&quot;&gt;同时产生合法区块怎么办&lt;/h3&gt;&lt;p&gt;但是，因为挖矿是不确定的，所以有可能会出现两个节点同时挖出来了相同工作量的合法区块，如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/d01e1eab_btc_img_15.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;此时其他节点会接收A和B两个区块（因为他们的工作量一样大），但是，因为网络的不确定性，一定有一些节点是从BLOCK A 开始挖，有些节点是从 BLOCK B开始挖。假如BLOCK A后面先挖到了一个新区块，那么其他节点就认为以BLOCK A的那条链是最长合法链，从而丢弃BLOCK B。BLOCK B被丢弃后，它里面发放给对应账户的铸币交易也会失效。&lt;/p&gt;
&lt;p&gt;为了防止两个节点同时出现不知道以哪个节点交易为准的情况发生，在现实真实的支付场景中，比特币规定，只有BLOCK A后面新增六个区块后，才会认为BLOCK A中的交易是有效的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/853decfb_btc_img_16.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;所以，&lt;strong&gt;比特币是一个靠社区所有节点决定的共识游戏，而不是由某一个中心机构决策的主权游戏&lt;/strong&gt;。这或许就是去中心化魅力吧。&lt;/p&gt;
&lt;h1 id=&quot;一笔比特币交易的产生过程&quot;&gt;一笔比特币交易的产生过程&lt;/h1&gt;&lt;p&gt;通过本文讲述的共识机制和比特币数据结构，我们来尝试复原一下，当“Alice给Bob转账10 BTC”时，比特币网络中的所有节点，是如何变化的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/9fd2ee78_btc_img_17.svg&quot;&gt;&lt;/p&gt;
</content:encoded></item>
<item><title>面向开发者的 macOS 环境配置指南</title><link>https://wxxlamp.cn/2025/08/15/how-to-use-mac/</link><guid isPermaLink="true">https://wxxlamp.cn/2025/08/15/how-to-use-mac/</guid><pubDate>Fri, 15 Aug 2025 04:05:00 GMT</pubDate><description>详细介绍MacOS系统目录结构及作为开发工具的最佳实践，涵盖常用命令、环境配置和系统管理技巧，帮助开发者更好地使用Mac进行开发工作。</description><content:encoded>&lt;p&gt;一转眼在公司四周年了，公司给我换了一个mbp14作为新的主子。&lt;/p&gt;
&lt;p&gt;因为四年前刚接触Mac，所以不管安装什么都是下一步，导致有时候会发生莫名其妙的环境问题，什么mvn命令找不到，python版本不一致等等。正好趁着mbp13正式退役，抽时间了解下MAC系统。&lt;/p&gt;
&lt;p&gt;同时，自己也不清楚安装的各种数据究竟存储在哪里。所以，借用这个机会，系统性的整理和学习下MacOS的基本概念和操作。&lt;/p&gt;
&lt;p&gt;使用姿势可以直接看第二部分&lt;/p&gt;
&lt;h1 id=&quot;Mac的目录结构&quot;&gt;Mac的目录结构&lt;/h1&gt;&lt;p&gt;类unix系统，不用像windows一样进行磁盘分区，所以我们也不用担心C盘垃圾数据太多影响操作系统的运行。在MAC中下载数据的时候，我们也不用考虑究竟要下载到哪个磁盘，这无疑降低了我们的决策成本。但是作为新手，还是要先粗略的了解下MAC的目录结构。&lt;/p&gt;
&lt;h2 id=&quot;1-1-系统目录&quot;&gt;1.1 系统目录&lt;/h2&gt;&lt;p&gt;系统目录在平时的使用中几乎接触不到，常见的可能有以下几个：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/Applications&lt;/code&gt;一些所有用户共享的app会安装在本目录下（使用homebrew会默认安装应用到该系统）&lt;/p&gt;
&lt;p&gt;下面的表格中是一些系统目录常见的功能，其中前四个是与日常开发和使用相关的，其他的关系不大。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;目录名&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;主要功能&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;特点&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;权限要求&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;常见用法&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;/Applications&lt;/td&gt;
&lt;td&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;/bin&lt;/td&gt;
&lt;td&gt;基本系统命令&lt;/td&gt;
&lt;td&gt;核心可执行文件&lt;/td&gt;
&lt;td&gt;系统级保护&lt;/td&gt;
&lt;td&gt;ls, cp, mv等基础命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/Volumes&lt;/td&gt;
&lt;td&gt;挂载点&lt;/td&gt;
&lt;td&gt;外部设备挂载&lt;/td&gt;
&lt;td&gt;系统管理&lt;/td&gt;
&lt;td&gt;USB、网络驱动器访问点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/opt&lt;/td&gt;
&lt;td&gt;第三方软件包&lt;/td&gt;
&lt;td&gt;可选软件安装&lt;/td&gt;
&lt;td&gt;需要管理员权限&lt;/td&gt;
&lt;td&gt;Homebrew等包管理器使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/System&lt;/td&gt;
&lt;td&gt;macOS核心系统文件&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;/Library&lt;/td&gt;
&lt;td&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;/Users&lt;/td&gt;
&lt;td&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;/sbin&lt;/td&gt;
&lt;td&gt;系统管理命令&lt;/td&gt;
&lt;td&gt;管理员专用命令&lt;/td&gt;
&lt;td&gt;需要root权限&lt;/td&gt;
&lt;td&gt;系统维护和配置工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/usr&lt;/td&gt;
&lt;td&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;/etc&lt;/td&gt;
&lt;td&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;/var&lt;/td&gt;
&lt;td&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;/tmp&lt;/td&gt;
&lt;td&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;/dev&lt;/td&gt;
&lt;td&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;/private&lt;/td&gt;
&lt;td&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;/home&lt;/td&gt;
&lt;td&gt;用户目录符号链接&lt;/td&gt;
&lt;td&gt;指向/Users&lt;/td&gt;
&lt;td&gt;系统管理&lt;/td&gt;
&lt;td&gt;兼容Unix系统的用户目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/cores&lt;/td&gt;
&lt;td&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;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;1-2-用户目录&quot;&gt;1.2 用户目录&lt;/h2&gt;&lt;p&gt;日常的工作区基本都在 &lt;code&gt;/Users/$user/&lt;/code&gt;目录下。所以我们日常数据的读写基本都在该目录下面。和windows系统一样，Mac推荐了一些常见的使用目录，供我们存放数据：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;目录名&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;主要功能&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;特点&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;推荐用法&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;~/Applications&lt;/td&gt;
&lt;td&gt;用户个人应用&lt;/td&gt;
&lt;td&gt;仅当前用户可用&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;安装个人应用的最佳位置&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~/Documents&lt;/td&gt;
&lt;td&gt;个人文档&lt;/td&gt;
&lt;td&gt;用户主要工作区&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;存储个人文档、项目文件、代码&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~/Downloads&lt;/td&gt;
&lt;td&gt;下载文件&lt;/td&gt;
&lt;td&gt;浏览器默认下载位置&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;下载文件的默认存储位置&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~/Desktop&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;~/Library&lt;/td&gt;
&lt;td&gt;用户配置和数据&lt;/td&gt;
&lt;td&gt;应用程序配置&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;开发者配置文件存储位置&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~/Pictures&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;~/Movies&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;~/Music&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;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;1-3-常用配置文件&quot;&gt;1.3 常用配置文件&lt;/h2&gt;&lt;p&gt;除了常见的目录外，用户根目录还有一些很重要的配置文件，这些文件会在终端session更新的时候加载，我们常见的非系统命令（如java、mvn、node）等都是通过这些文件在session初始化时更细的。&lt;/p&gt;
&lt;p&gt;其中在Mac Catalina之前，使用的是.bash_profile和.bashrc，现在这两个文件只作为兼容保留，推荐在Mac Catalina之后使用.zprofile和.zshrc。他们有什么作用呢？&lt;/p&gt;
&lt;p&gt;当我们打开terminal.app时，系统会默认执行一遍.bash_profile和.zprofile里面的脚本&lt;/p&gt;
&lt;p&gt;具体的执行顺序如下：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;login → /etc/zshenv → ~/.zshenv → /etc/zprofile → ~/.zprofile → /etc/zshrc → ~/.zshrc → /etc/zlogin → ~/.zlogin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;总的来说，第一次打开时，执行.zprofile，以后新开窗口（如在vscode中打开terminal）时执行.zshrc。所以推荐将全局变量和PATH写到.zprofile中，将函数、alias写到.zshrc中。注意，不建议把PATH或者JAVA_HOME写到.zprofile中，这样有可能会导致PATH路径重复且特别长，影响which查找效率。同时，GUI程序也会默认读取.zprofile的配置&lt;/p&gt;
&lt;p&gt;具体的分类如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;文件名&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Shell类型&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;加载时机&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;主要用途&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;当前状态&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.zprofile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zsh&lt;/td&gt;
&lt;td&gt;登录时加载一次&lt;/td&gt;
&lt;td&gt;登录初始化、GUI读取&lt;/td&gt;
&lt;td&gt;🟢 推荐使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.zshrc&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zsh&lt;/td&gt;
&lt;td&gt;每次新终端会话&lt;/td&gt;
&lt;td&gt;Zsh交互式会话配置&lt;/td&gt;
&lt;td&gt;🟢主要配置文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.bash_profile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bash&lt;/td&gt;
&lt;td&gt;登录时加载一次&lt;/td&gt;
&lt;td&gt;Bash shell环境配置&lt;/td&gt;
&lt;td&gt;🟡 兼容性保留&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果我们希望脚本的配置生效，只需要执行下面的命令即可：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;source&lt;/span&gt; ~/.zshrc&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;1-4-隐秘的文件夹&quot;&gt;1.4 隐秘的文件夹&lt;/h2&gt;&lt;p&gt;在MAC的用户根目录中，有很多配置文件夹，以&lt;code&gt;.xx&lt;/code&gt;开头，他们一般是存储各种程序（pyenv,git,nvm,vscode等）的基础配置，如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/b7b51d7e_mac_folders.png&quot; alt=&quot;Mac隐秘文件夹结构&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;如何下载常用的工具&quot;&gt;如何下载常用的工具&lt;/h1&gt;&lt;p&gt;Mac的下载工具方式大体和windows一样，都包含app store和通过浏览器下载安装包两种方式。但是除此之外，Mac作为类unix系统，还是有一些更“高效”的下载方式&lt;/p&gt;
&lt;h2 id=&quot;常用的下载工具&quot;&gt;常用的下载工具&lt;/h2&gt;&lt;p&gt;把mac作为开发工具，一定会下载很多没有GUI的程序，这种情况下，就不推荐用浏览器下载了。一般可以通过homebrew和curl下载。比较推荐homebrew，他是Mac专门用来管理软件包的工具，我们可以通过这个工具对常见的软件进行包管理、安装、删除、升级等，避免自己将来在删除程序的时候有删除不干净的情况。但是，请注意，homebrew无法安装同一软件的多个版本。譬如电脑上既存在python2和python3，这借助homebrew的能力是实现不了的，可以通过pyenv等专用工具实现，这个下文会讲到&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;使用场景&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;推荐工具&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;原因&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;存储目录&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;日常文件下载&lt;/td&gt;
&lt;td&gt;Safari/Chrome&lt;/td&gt;
&lt;td&gt;简单易用，安全可靠&lt;/td&gt;
&lt;td&gt;&lt;del&gt;/Downloads/和&lt;/del&gt;/Library/Application Support/Google/Chrome和~/Applications/Chrome Apps/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;批量/自动化下载&lt;/td&gt;
&lt;td&gt;curl&lt;/td&gt;
&lt;td&gt;脚本化，可定制&lt;/td&gt;
&lt;td&gt;通过&lt;code&gt;-o&lt;/code&gt;制定目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI软件安装&lt;/td&gt;
&lt;td&gt;App Store&lt;/td&gt;
&lt;td&gt;官方集成&lt;/td&gt;
&lt;td&gt;&lt;del&gt;/Library/Application Support/App Store/和&lt;/del&gt;/Applications/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;命令行工具下载&lt;/td&gt;
&lt;td&gt;Homebrew&lt;/td&gt;
&lt;td&gt;包统一管理，类似于apt,pyenv,nvm&lt;/td&gt;
&lt;td&gt;/opt/homebrew/&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;常见的homebrew命令&quot;&gt;常见的homebrew命令&lt;/h2&gt;&lt;p&gt;先列举下homebrew的常见命令：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 查看Homebrew安装路径&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew --prefix&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 查看某个包的安装位置&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew --prefix &amp;lt;package-name&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 清理缓存&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew cleanup&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 查看Homebrew占用空间&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;du&lt;/span&gt; -sh $(brew --prefix)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#删除缓存文件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;rm&lt;/span&gt; -rf &lt;span class=&quot;string&quot;&gt;&quot;&lt;span class=&quot;subst&quot;&gt;$(brew --cache)&lt;/span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#重新安装损坏的软件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew reinstall git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 查看安装的软件及版本&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew list --versions&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#每日维护例程&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew update &amp;amp;&amp;amp; brew upgrade &amp;amp;&amp;amp; brew cleanup&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#搜索并安装软件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew search python&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew info python@3.11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew install python@3.11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 服务管理&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew install mysql&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew services start mysql&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;brew services list&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如果使用homebrew的话，要注意设置国内的镜像。&lt;/p&gt;
&lt;h1 id=&quot;常用的Mac-App&quot;&gt;常用的Mac App&lt;/h1&gt;&lt;p&gt;这里列举下常用的app以及他们的安装方式&lt;/p&gt;
&lt;h2 id=&quot;日常&quot;&gt;日常&lt;/h2&gt;&lt;table&gt;
&lt;thead&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;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;浏览器/主&lt;/td&gt;
&lt;td&gt;brew、浏览器、appStore&lt;/td&gt;
&lt;td&gt;主浏览器使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;浏览器/备&lt;/td&gt;
&lt;td&gt;brew、浏览器、appStore&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;brew、浏览器、appStore&lt;/td&gt;
&lt;td&gt;记忆比mac自带好，但是要小心泄露&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;微信&lt;/td&gt;
&lt;td&gt;通讯&lt;/td&gt;
&lt;td&gt;brew、浏览器、appStore&lt;/td&gt;
&lt;td&gt;Mac可以双开&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maccy&lt;/td&gt;
&lt;td&gt;剪切板&lt;/td&gt;
&lt;td&gt;brew、github&lt;/td&gt;
&lt;td&gt;存储近50条粘贴的记录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scroll Reverser&lt;/td&gt;
&lt;td&gt;鼠标翻转&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://scroll-reverser.macupdate.com/&quot;&gt;浏览器&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;外接鼠标的滚轮方向和windows一致&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;效率工具&quot;&gt;效率工具&lt;/h2&gt;&lt;table&gt;
&lt;thead&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;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Item2&lt;/td&gt;
&lt;td&gt;终端&lt;/td&gt;
&lt;td&gt;brew、浏览器&lt;/td&gt;
&lt;td&gt;比Mac自带的Terminal好用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ohmyzsh&lt;/td&gt;
&lt;td&gt;zsh自定义&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh&quot;&gt;github&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;更换主题，安装高亮、自动补全、快捷跳转目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idea&lt;/td&gt;
&lt;td&gt;Java开发&lt;/td&gt;
&lt;td&gt;浏览器、Jetbrain Toolbox&lt;/td&gt;
&lt;td&gt;Jetbrains Jvm系开发全家桶（rainbow插件）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pycharm&lt;/td&gt;
&lt;td&gt;Pyhon开发&lt;/td&gt;
&lt;td&gt;浏览器、Jetbrain Toolbox&lt;/td&gt;
&lt;td&gt;Jetbrains Python开发全家桶&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebStorm&lt;/td&gt;
&lt;td&gt;Js开发&lt;/td&gt;
&lt;td&gt;浏览器、Jetbrain Toolbox&lt;/td&gt;
&lt;td&gt;Jetbrains Web开发全家桶&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vscode&lt;/td&gt;
&lt;td&gt;编辑文本&lt;/td&gt;
&lt;td&gt;brew、浏览器&lt;/td&gt;
&lt;td&gt;python、Js开发（命令行直接code xx唤醒超方便）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;light proxy&lt;/td&gt;
&lt;td&gt;代理工具&lt;/td&gt;
&lt;td&gt;github&lt;/td&gt;
&lt;td&gt;开发流量监控&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/bannedbook/ClashX&quot;&gt;clashX&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/bannedbook/ClashX&quot;&gt;github&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;代理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;虚拟化&lt;/td&gt;
&lt;td&gt;brew&lt;/td&gt;
&lt;td&gt;cask和命令行都可以&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;对于ohmyzsh来说，需要安装一些插件才可以更好的使用：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更换&lt;a href=&quot;https://github.com/spaceship-prompt/spaceship-prompt&quot;&gt;spaceship主题&lt;/a&gt;，安装&lt;a href=&quot;https://github.com/zsh-users/zsh-syntax-highlighting/tree/master&quot;&gt;zsh-syntax-highlighting&lt;/a&gt;插件，高亮显示命令等；安装&lt;a href=&quot;https://github.com/zsh-users/zsh-autosuggestions&quot;&gt;zsh-autosuggestions&lt;/a&gt;插件，历史命令自动补全；安装&lt;a href=&quot;https://github.com/wting/autojump&quot;&gt;autojump&lt;/a&gt;插件，快捷跳转目录&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;开发环境&quot;&gt;开发环境&lt;/h2&gt;&lt;h3 id=&quot;git支持多仓库&quot;&gt;git支持多仓库&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;通过ssh工具，生成两个git秘钥和公钥，将生成后的公钥复制到对应的多个仓库设置中&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;ssh-keygen -t rsa -b 4096 -C &quot;wxxlamp@foxmail.com&quot; -f ~/.ssh/id_rsa_person&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;ssh-keygen -t rsa -b 4096 -C &quot;wxxlamp@work.com&quot; -f ~/.ssh/id_rsa_work&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;同时将这两个密钥分别配置在gitlab仓库和github后台上，然后配置.ssh/config如下&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# GitHub&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Host github.com&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    HostName github.com&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    User wxx&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 对应工作仓库&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Host gitlab.work.com&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    HostName gitlab.work.com&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    User work&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    IdentityFile ~/.ssh/id_rsa_work&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;使用&lt;code&gt;ssh -T@github.com&lt;/code&gt;进行联通性测试&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;Java-Maven-Runtime&quot;&gt;Java&amp;amp;Maven Runtime&lt;/h3&gt;&lt;p&gt;因为homebrew不支持单app多版本，所以像Java、Python、Node等环境需要特殊的工具配置。&lt;/p&gt;
&lt;p&gt;对于Jvm系来说，可以使用sdkman全局管理不同版本的Java和maven配置：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 1. 安装 SDKMAN&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;curl -s &lt;span class=&quot;string&quot;&gt;&quot;https://get.sdkman.io&quot;&lt;/span&gt; | bash&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;span class=&quot;variable&quot;&gt;$HOME&lt;/span&gt;/.sdkman/bin/sdkman-init.sh&quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 2. 查询并安装 Java 21&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sdk list java&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sdk install java 21.0.4-tem&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 3. 设为默认&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sdk default java 21.0.4-tem&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 4. 安装 Maven 最新版&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sdk install maven&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 5. 查看结果&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sdk current&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;常见的sdkman命令&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;示例 / 备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;查看所有可装 candidate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;列出支持的语言/工具&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk list&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;查看某个 candidate 的所有版本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;例如 java&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk list java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;安装指定版本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;安装 openjdk 17&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk install java 17.0.12&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;安装并使用最新稳定版&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;省略版本号即可&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk install maven&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;切换到已安装的某个版本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;仅对当前 shell 生效&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk use java 21.0.4-tem&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;设置默认版本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;全局生效（写入 ~/.sdkmanrc）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk default java 21.0.4-tem&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;查看当前正在使用的版本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;列出所有 candidate 的当前版本&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk current&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;查看单个 candidate 当前版本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;只看 java&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk current java&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;卸载某个版本&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;彻底删除本地副本&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk uninstall java 11.0.25&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;查看环境变量路径&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;调试/脚本中常用&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sdk home java 17.0.12-tem&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;Python-runtime&quot;&gt;Python runtime&lt;/h3&gt;&lt;p&gt;python市面上的管理器是在太多了。同时，要注意，MAC出厂就会自带python运行时，在&lt;code&gt;/usr/bin/python3&lt;/code&gt;目录下。所以如果不是开发python项目，使用Mac自带即可。但是很多程序对python的版本都有要求，所以我们要通过第三方工具来完成python版本的切换和包管理。令人遗憾的是，许多其他的python版本和包管理工具，对于初学者来说，不眼花缭乱是不可能的，如下所示：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;工具&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;核心作用&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;管理范围&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;典型场景&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;venv&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;轻量级虚拟环境管理&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;仅虚拟环境（依赖隔离）&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;简单项目，依赖 Python 3.3 + 内置功能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;virtualenv&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;增强版虚拟环境管理&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;仅虚拟环境（依赖隔离）&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;多项目依赖隔离，支持 Python 2/3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;pyenv&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Python 版本管理&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;仅 Python 解释器版本&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;多版本 Python 共存切换&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;conda&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;环境管理（不止 Python）&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;版本管理 + 虚拟环境 + 包管理&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;数据科学、跨语言项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;一句话解释，pyenv是用来切换python版本的，virtualenv是提供虚拟环境，各个环境下载的python依赖相互隔离。conda是virtualenv的增强版，可以管理除了python以外其他科学计算的包&lt;/p&gt;
&lt;p&gt;推荐：使用pyenv做全局的python版本管理，同时使用pyenv-virtualenv做python的虚拟环境管理。除此之外，把conda作为pyenv的一个虚拟环境，如果项目需要使用非python包，则通过pyenv切换到conda环境。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里可以稍微聊下几种工具提供虚拟环境的机制。&lt;/p&gt;
&lt;p&gt;对于pyenv来说，首先要保证python的PATH指向pyenv，如果设置global的python版本，本质是在&lt;code&gt;~/.pyenv/version&lt;/code&gt;文件中设置python版本。如果是设置local的python版本，说明只在当前文件夹生效，本质是在文件夹的根目录创建一个&lt;code&gt;.python-version&lt;/code&gt;的文件，里面记录了python的版本&lt;/p&gt;
&lt;p&gt;对于virtualenv来说，当我们激活当前虚拟环境，他会在当前目录新建一个venv目录，该目录会存储当前虚拟环境的所有依赖（python版本仍然使用的pyenv指定的版本，不会新copy一份）。如果关闭shell，下次打开需要重新激活环境&lt;/p&gt;
&lt;p&gt;对于conda来说，当我们激活当前虚拟环境，他会在conda安装目录新建一个虚拟环境目录，并且会copy一份python版本，同时后续的所有依赖也会存储在该目录。如果关闭shell，下次打开需要重新激活环境&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;常见命令行如下&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;确认当前python的路径&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;which python3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;======= pyenv &lt;span class=&quot;built_in&quot;&gt;command&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;List all installed Python versions &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; pyenv&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pyenv versions&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;Install a specific Python version (&lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; not already installed):&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pyenv install 3.11.4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;set&lt;/span&gt; the python system or current versions&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pyenv global 3.11.4  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cd /path/to/your/project&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pyenv local 3.10.8  # Creates a .python-version file in the project&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;======= conda &lt;span class=&quot;built_in&quot;&gt;command&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;通过pyenv管理conda环境&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pyenv install miniconda3-latest&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;shell级别切换python版本&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pyenv shell miniconda3-latest&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;List all conda environments:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;conda env list&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;Create a new environment with a specific Python version&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;conda create --name myenv python=3.9 &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;Activate an environment&lt;/span&gt; &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;conda activate myenv&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta prompt_&quot;&gt;# &lt;/span&gt;&lt;span class=&quot;language-bash&quot;&gt;查看当前pip安装的依赖地址&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;pip show pip | grep Location&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Node-runtime&quot;&gt;Node runtime&lt;/h3&gt;&lt;p&gt;可以使用nvm进行管理node的版本。nvm推荐直接使用curl下载&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;nvm install --lts     # 安装最新的 LTS 版本&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;nvm install 20.12.2   # 安装指定版本&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;nvm use 20.12.2       # 切换到指定版本&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;nvm current&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;nvm list&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;



</content:encoded></item>
<item><title>Maven 编译编码异常排查：Malformed Unicode Escape</title><link>https://wxxlamp.cn/2025/06/18/maven-malformed-exception-solution/</link><guid isPermaLink="true">https://wxxlamp.cn/2025/06/18/maven-malformed-exception-solution/</guid><pubDate>Wed, 18 Jun 2025 10:30:00 GMT</pubDate><description>本文详细记录了一次Maven编译时遇到Malformed \uxxxx异常的排查过程，通过分析堆栈、调试源码，最终定位到是本地Maven仓库中resolver-status.properties文件的编码问题，并提供了有效的解决方案。</description><content:encoded>&lt;h1 id=&quot;问题现象&quot;&gt;问题现象&lt;/h1&gt;&lt;p&gt;笔者在一次需求迭代的时候，从仓库拉下来了一个分支，然后肌肉记忆般的使用mvn reimport重新sync其他的依赖。本来以为这个已经点了几千次的操作会像往常一样顺利加载依赖，然后进入我最开心的写代码环节。结果却出现了下面的异常：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/70c01355_c527162f.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;看到这个异常一脸懵逼，难道是新拉下来的分支pom文件的编码有问题？&lt;/p&gt;
&lt;p&gt;伴随着这个怀疑，开始了问题排查之旅。&lt;/p&gt;
&lt;h1 id=&quot;问题排查&quot;&gt;问题排查&lt;/h1&gt;&lt;h2 id=&quot;Step-1-确认代码是否有问题&quot;&gt;Step 1. 确认代码是否有问题&lt;/h2&gt;&lt;p&gt;打开应用的pom文件，发现没有修改过。同时为了double check，将该分支在aone上重新部署，没有出现编译异常，说明远程仓库的代码没有问题，是自己环境的问题。&lt;/p&gt;
&lt;h2 id=&quot;Step-2-确认是否是IDE问题&quot;&gt;Step 2. 确认是否是IDE问题&lt;/h2&gt;&lt;p&gt;既然代码没有问题，那就可能是IDE的缓存导致的，所以将IDE的缓存进行清空并且restart。为了保险起见，还特意删除了应用本地的仓库，重新git clone。但是不幸的是，依然没有解决这个问题。&lt;/p&gt;
&lt;h2 id=&quot;Step-3-确认是否是依赖问题&quot;&gt;Step 3. 确认是否是依赖问题&lt;/h2&gt;&lt;p&gt;现在回过头来思考，既然工程代码没有编码问题、IDE缓存也没有问题，那使用mvn refresh为什么要抛出&lt;code&gt;Malformed \uxxxx&lt;/code&gt;异常呢？是否是加载其他二方包依赖的时候，解析二方包的文件导致的？&lt;/p&gt;
&lt;p&gt;带着这个问题，尝试google了下错误的内容，果然在stackflow中找到了解决方案，基本确定是maven解析repo依赖产生了问题&lt;/p&gt;
&lt;h1 id=&quot;问题原因&quot;&gt;问题原因&lt;/h1&gt;&lt;p&gt;使用 &lt;code&gt;mvn compile -e&lt;/code&gt;查看具体的堆栈，发现maven在解析的时候有问题：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/7a0d93ee_c4ed09dd.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;借用IDEA自带的maven debug功能，进入&lt;code&gt;java.util.Properties&lt;/code&gt;开始debug&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/f866f3f1_0e220e30.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;发现确实有的依赖是乱码：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/c8d7adad_db686eae.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;往上找堆栈，发现下面这个文件确实有问题：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/0bfcdf39_005cedd1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;打开文件一看，果然有下面的乱码：&lt;br&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/13d2a10b_9c90a981.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;将这个文件删除就好了。&lt;/p&gt;
&lt;p&gt;然后重新import后，这个文件会重新生成：&lt;br&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/5da8029e_88a7ecd2.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;发现已经没有这些乱码了。受到影响的项目也可以顺利build了，完结撒花。&lt;/p&gt;
&lt;h1 id=&quot;问题思考&quot;&gt;问题思考&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;大部分遇到的问题，Google和GPT都能处理。举一反三，内部中间件问题搜索下语雀和ATA可能也会有意想不到的收获。&lt;/li&gt;
&lt;li&gt;印象中大学时候应该遇到过这个问题，当时只是简单的删除了repo重新refresh就OK了，没有深入排查。同时，更加深刻地理解了好记性不如烂笔头，遇到问题还是到记录一下。&lt;/li&gt;
&lt;li&gt;resolver-status.properties的作用是什么？为什么删除resolver-status.properties就OK了？LLM答案如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;resolver-status.properties文件是Maven依赖解析器的元数据文件，主要用于&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;跟踪依赖解析状态、缓存解析结果、记录仓库来源并优化构建性能&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;。&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;文件中出现\u0000（null字符）表示文件损坏，可能由&lt;/font&gt;&lt;strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;构建中断、磁盘空间不足、并发访问冲突或文件系统问题导致&lt;/font&gt;&lt;/strong&gt;&lt;font style=&quot;color:rgba(0, 0, 0, 0.88);&quot;&gt;。重新生成的文件正常是因为Maven会根据当前依赖信息重新创建正确的元数据，不包含损坏数据，这种自我修复机制确保了构建的健壮性。&lt;/font&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;如何避免避免mvn构建过程中的resolver-status.properties问题？如果系统都正常的话，可能是并发构建导致，此时可以在IDE中设置如下参数，保证构建线程数为1，亲测对于中型工程来说，构建速度并没有减少太多&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/c0625668_fba539ac.png&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;之前没用过IDEA对MVN的构建过程进行debug，这次正好尝试一下，后面涉及到MVN命令的异常，又多了一种解决途径。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;参考&quot;&gt;参考&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/17043037/ant-malformed-uxxxx-encoding-in-propertyfile-task&quot;&gt;https://stackoverflow.com/questions/17043037/ant-malformed-uxxxx-encoding-in-propertyfile-task&lt;/a&gt;&lt;/p&gt;
</content:encoded></item>
<item><title>2024 年度回顾：晋升、半马与港深求学</title><link>https://wxxlamp.cn/2025/03/07/my-2024/</link><guid isPermaLink="true">https://wxxlamp.cn/2025/03/07/my-2024/</guid><pubDate>Fri, 07 Mar 2025 12:19:00 GMT</pubDate><description>2024年终总结：成功晋升、首次半马跑出1小时46分PB。收到港科大录取后选择转岗深圳，开启港深双城通勤生活。学期末三门课全拿A，积累大量金融与跨境支付知识，在全新环境中摸索方向。</description><content:encoded>&lt;p&gt;说来惭愧，年度总结一年写的比一年晚。虽然时间就像是海绵里的水，只要肯挤总会有的，但是deadline才是第一生产力。今年的年度总结，竟然到25年3月底才开始动笔。&lt;/p&gt;
&lt;p&gt;悟以往之不谏，知来者之可追。希望2025年的年终总结不会像24年一样拖到很晚。&lt;/p&gt;
&lt;p&gt;2024年整体来说是新鲜、充实和迷茫，接触了新的环境，工作模式、学习方式都与之前发生了改变。申请到了科大的MSc，转岗到了深圳，开始了一段港深通勤的生活。脱离了在杭州时候稳定的人际关系和工作环境，适应期难免有点波折，但是心中又夹杂着跃跃欲试的冲动。&lt;/p&gt;
&lt;h3 id=&quot;2024年目标达成情况&quot;&gt;2024年目标达成情况&lt;/h3&gt;&lt;p&gt;以前年年都是里程碑，今年换一种总结方式，分为两部分，一部分是回顾自己2024年目标的达成情况，另一部分是列举下自己的实际做了什么事情。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;稳定的作息：8点半起床，晚上12点睡觉。工作日无特殊情况至少1个半小时在学习和写作上，周末无特殊情况至少6个小时在学习和写作上；&lt;/strong&gt;===&amp;gt; 40%，因为下半年读书的原因，基本早上9点半起，晚上2点半睡觉，实际和规划不同，完全歇菜。不过工作日和周末的学习、写作时间基本都是可以保证的，但是实际产出也有点不及预期&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;继续运动：每周6次引体+卷腹，每次2-3组，年底最少4块腹肌。全年跑步路程800KM&lt;/strong&gt; ===&amp;gt; 70%。卷腹引体基本都可以保持，4块腹肌在年中已经实现，不过年底就退回去了。全年跑步累计764KM，稍微不及预期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完善高并发场景热点系统的处理体系；12篇技术博客&lt;/strong&gt; ===&amp;gt; 0%，这个事情真的是一直想梳理的，因为他一定是面试必问的，说实话，写这篇文章的时候都忘记有这么个事情了&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习IOS和Vision pro开发&lt;/strong&gt;（待定）===&amp;gt; 0%，去年开年，Vision PRO真的超级火，和现在的deepseek一样。但是过了几个月之后就了无音讯了，所以这件事情也无疾而终&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对国内金融有基础的了解&lt;/strong&gt; ===&amp;gt; 90%，因为转岗到global团队，要负责整个交易和资金履约链路，所以24年下半年被动接收到了很多金融、经济和支付知识&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阅读2本小说&lt;/strong&gt;（待定）===&amp;gt; 100%，大明王朝基本看完了，甚至看的时候留意到了一些熟悉的人物，还想把这些人物关系通过网站实现出来，但是想法太多，行动太少，所以这个也没落实。其次看了陈行甲的江湖转弯处，还有小巷人家，都写的不错，自己也挺有感触的。尤其是小巷人家，小说中描写的任务冲突和家庭矛盾，有种令人身临其境的感觉，也能学到一点可能会用到的解决之道。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习基础的AI知识，能够利用大模型进行基础的数据建模&lt;/strong&gt; ===&amp;gt; 10%，LLM很火，但是可能是太忙了，对LLM的知识是被动接收，还没有主动吸收过&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;副业方面，建立以知识创作和个人思考的自媒体平台，本年度全平台粉丝1000人&lt;/strong&gt;。===&amp;gt; 50%，全网加起来一共500粉丝，基础的媒体矩阵搭建起来了，下面就是专心、用心的更新post了&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其实可以看到，24年的目标完成率不足50%。一方面反映出来自己执行力太差，另一方面反映的是事情变化的太快了，完全的计划赶不上变化。所以新的一年要加强反馈手段，更及时的调整目标，纠正方向。&lt;/p&gt;
&lt;p&gt;除了通过24年既定目标按图索骥之外，整个25年我还做了这几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;入学HKUST，成为半个男大&lt;/li&gt;
&lt;li&gt;转岗到深圳，负责对接欧洲、亚洲、美洲商家的全球交易和资金履约业务&lt;/li&gt;
&lt;li&gt;考高级软件工程师，结果差了几分没考上&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;2024复盘总结&quot;&gt;2024复盘总结&lt;/h3&gt;&lt;p&gt;和去年一年，上半年的内容几乎回忆不起来了。幸好有写日记的习惯，还能重新回顾下以前发生的事情，但是日记太多，时间太碎，所以新年我准备除了写日记，还要写月记，把每个月的事情都记录下来，以此思考沉淀，及时反馈改正。&lt;/p&gt;
&lt;p&gt;或许是因为工作一直在南方的原因，我也慢慢了解了南方的过年习俗。南方过年是和我们那里不一样的，我们那边没有年夜饭，也没有旅游各种项目。打我记事起，过年是从寒假开始开始的，买玩具枪，买子弹，买烟花，每天都在外面疯跑，一直到大年三十开始贴春联，后面的大年初一是正式过年了。&lt;/p&gt;
&lt;p&gt;24年的一月份我主要做了两件事，一件就是申请NTU和NUS的研究生，第二件事就是过春节。整个春节，主线任务和以前也没什么区别，大年初一去拜年，初二到初五都是去我妈和我奶家的兄弟姐妹们。以前过年初六、七、八还是能休息几天的，但是今年不一样，因为我弟的原因，在这三天里我想方设法帮他找关系给他调换到更重点的班里面，但是最后还是失败了。整个一月，就这么过去了。&lt;/p&gt;
&lt;p&gt;二月份到公司，基本就要坐一件事情，就是写review，应付马上要来的绩效季。我其实不是很担心，因为我知道今年我肯定能晋升。除此之外，工作事业也有了扩展——做了一点交易和合同的事情，对B2B跨境交易有了一点了解。&lt;/p&gt;
&lt;p&gt;三月份顺利成章的完成晋升，同时自己也报名了软考。最重要的是，参加了自己人生中的第一次半程马拉松，实现了个人PB：1小时46分钟。同时，三月份也在思考如何拓展自己的副业，结论是先把自己的雅思笔记发到小红书上，但是很显然，最后没有什么水花。&lt;/p&gt;
&lt;p&gt;四月份主要是内耗的一个月，因为自己业务时间的利用效率实在是太低了，周末的一天，基本就只能全身心的投入3哥小时学习或者写作，早上起不来，下午不想起，运动一会看会手机就又该回去睡觉了。阿里的年终一般是4月份发放，看了一下，涨幅也没多少，看来互联网确实和高速增长挂不上边了。&lt;/p&gt;
&lt;p&gt;五月份开始复习软考，但是最后差了十分，和高级软件架构师证书失之交臂。第二件事就是工作上主要在做的系统迁移工作，基本已经到了收尾阶段，这个感觉还是挺用心的。最后一件事决定了我整个24年的转折，就是收到了UST的offer，下offer的时间实在是太晚了，我本来都不准备去了，后来仔细思考，其实还是因为自己有一个研究生梦，还是决定辞职去读书。这个思考过程几乎持续了一个月，期间询问了很多人，各种建议，整个流程真的很痛苦，每天几乎什么正经事都干不了。&lt;/p&gt;
&lt;p&gt;六月份整体来说，就是通知身边的朋友们我即将离开阿里，要去香港读书了。准备各种去香港的规划：搞副业、学英语、找香港工作等等&lt;/p&gt;
&lt;p&gt;七月份事情又迎来了转机，在准备入学的过程中，突然发现自己好像可以转岗到深圳，然后在主管和HR的帮忙下，我成功rebase到深圳的全球卖团队，有了一个既可以工作，又可以上学，看似双赢的结果。不过不用辞职总归是好的，然后自己就开始熟悉深圳团队的业务了。除此之外，自己又重新搞起了八股文校招版，重新梳理了一下适合校招生的后端面试题，感觉还是挺有成就感的。另外还做了一件影响我整个港深通勤计划的事情，就是我修了EDX，去UST上课的时候可以少上三门课，这个决定可以说是在后来救了我的大命了。&lt;/p&gt;
&lt;p&gt;八月份除了延续七月份的事情，把EDX认证了之后，于8月16号正式rebase到了深圳，开始了可能一年的深圳之旅。八月下旬，基本就是熟悉深圳公司的情况、租房子、去香港熟悉学校，一切都是新鲜的感觉。&lt;/p&gt;
&lt;p&gt;九月份正式开始了港深通勤之旅。一直到十一月学期结束，我需要每周从深圳去三次香港。作业、上课、加上全职的工作，简直忙到飞起，日记都没写几个字。这期间发生了很多有意思的事情，第一次去香港，第一次办信用卡和港卡，第一次办香港身份证，第一次仰望接近100层的居民楼。在上学的过程中，了解了区块链这种东西，但是老师没有教的太深入，不是很偏实际的应用，再加上印度老师的口语十分难以理解，我也就没有继续跟进。除此之外，与我本科教育的对比，我发现香港这种境外高校在教育上确实是有两把刷子的：第一，他们的教育资源很丰富，图书馆直接4K的显示屏免费用；第二，教学内容：老师上课直接把论文的perspective作为课堂的内容；第三，行政管理：境外学校的行政管理和境内是天壤之别，课堂ppt、录像和作业都是通过canvas交流，学校有什么事情也是通过邮件沟通，而不是像内地一样，各种资料qq群满天飞。因为经常在香港，所以被动接受了很多金融知识，包括但不限于外汇、投资、支付等等。自己也在香港卡上放了2w左右。不过还是有一点遗憾的，学校授课是全英的，很多老外，但是我羞于表达，英语的口语和听力几乎原地踏步，感觉日常沟通都有点费劲。&lt;/p&gt;
&lt;p&gt;十二月接连三场考试，熬了几个大夜，但是总归拿了三个A，毕业应该是没问题了。十二月下旬和团队去北京团建，没有考试压力旅游的压力，真棒。&lt;/p&gt;
&lt;h3 id=&quot;2025FLAG&quot;&gt;2025FLAG&lt;/h3&gt;&lt;p&gt;因为去香港读书的原因，导致自己整个24年的轨迹其实并没有按照预期来走，不过25年大体的轨迹还是既定的。希望今年的任务自己能够好好完成。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;工作上，梳理跨境交易和资金履约整体的业务链路和知识，写1-2篇文档&lt;/li&gt;
&lt;li&gt;开发上，完善高并发场景热点系统的处理体系，写2-4篇文档&lt;/li&gt;
&lt;li&gt;技术上，掌握ML-&amp;gt;DL-&amp;gt;NLP-&amp;gt;LLM的基础知识，使用Spring AI/LangChain搭建一个成熟的产品，写1-2篇文档&lt;/li&gt;
&lt;li&gt;学习上，拿到港硕的毕业证&lt;/li&gt;
&lt;li&gt;职业上，争取更上一层楼，具体指标提现在绩效上。没有其他事情影响，工作上要all in了&lt;/li&gt;
&lt;li&gt;理财上，平均年化3%，更具体一点说，要通过把部分资金换成美元存定期来达成&lt;/li&gt;
&lt;li&gt;心性上，聚焦1-2件重要的事情，停止发散。具体的规划可以立刻落地&lt;/li&gt;
&lt;li&gt;作息上，晚上12点半睡，早上8点半起&lt;/li&gt;
&lt;li&gt;运动上，本年度跑步700KM&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item>
<item><title>在阿里工作三年：业务、项目管理与个人成长</title><link>https://wxxlamp.cn/2025/02/16/ali-3-years-thought/</link><guid isPermaLink="true">https://wxxlamp.cn/2025/02/16/ali-3-years-thought/</guid><pubDate>Sun, 16 Feb 2025 12:19:00 GMT</pubDate><description>阿里工作三年后的非技术向复盘，围绕业务与技术平衡、项目管理、职场心态、拥抱变化等维度，分享从校招新人成长的真实感悟，适合入职大厂同学参考。</description><content:encoded>&lt;p&gt;二零二一年的七月九号，我以校招生的身份入职了阿里，开启了一段十分有意思、有意义的阿里旅程。&lt;/p&gt;
&lt;p&gt;这三年，我从企业金融技术部，到ICBU技术部，中间还借调到1688技术部一段时间。整个滨江园区的业务，除了已经仙去的零售通，我好像都经历了一遍。&lt;/p&gt;
&lt;p&gt;三年里，先后经历了5任老板，换过N个小组。经历过很多好的坏的、各种郁闷坑爹事情。但是幸好周围的各位大佬对我不厌其烦帮助、孜孜不倦地教诲，让我感受到了阿里人温暖，在这里收获了友情，成长和感动。&lt;/p&gt;
&lt;p&gt;在阿里有句俗语，三年成人，在这个时间点，我小小地做个总结，欢迎拍砖～&lt;/p&gt;
&lt;h2 id=&quot;搞业务与做技术&quot;&gt;搞业务与做技术&lt;/h2&gt;&lt;p&gt;“面试造火箭，入职拧螺丝”。&lt;/p&gt;
&lt;p&gt;在越来越卷的当下，面试的难度越来越高，HR先筛选一下简历，非985的不要，没项目的不要。面试阶段，大部分阿里的面试官一上来先来几道八股文练练手，再结合简历项目看看实力，最后问几道高并发完美收场。面试官的这套组合拳打完后，面试者真的会以为将来工作的场景会和这些高并发高可用休戚相关，拳打千万并发、脚踢亿级流量，一想到亿级用户将通过在下手中的代码完成一次次交易与支付，激动之情就难以言表，喜不自胜。&lt;/p&gt;
&lt;p&gt;但是入职之后却发现，面试时候信手拈来的各种高性能、高可用、高性能的奇技淫巧根本没有任何用武之地， 取而代之的是被PD各种强奸，CURD就是干，每天在N年前古人写下的屎山中猜测各种古老的业务细节，一个不好喜提P级故障，拉群、复盘一条龙。搞不好来坨大的喜提N+1直接输送社会。&lt;/p&gt;
&lt;p&gt;很多人入职都会进入业务团队，他们就会后发现，自己日常接触的代码，或许因为业务迭代的压力，或许困于前任设计的缺陷，几乎没有任何“技术”的设计可言，一言不合加机器、升配置，是最常见、也可能是唯一的处理所谓高并发的手段。&lt;/p&gt;
&lt;p&gt;至于高可用，中间件平台已经够完善了，根本不可能出现之前面试所说的极端问题——即使出现，也不在业务场景的考虑范围内，因为数据流量太小，离线核对订正往往是最常见的解决方案。&lt;/p&gt;
&lt;p&gt;所以，入职业务技术团队后，相信大部分校招同学，在开发一段时间业务需求之后，轻则怀疑公司，生出“阿里就这？”的感叹，然后麻木地被业务需求往前赶；重则道心破碎，开始怀疑自己的当初的梦想，最后要么被干要么跑路换行。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;我刚入职后就属于前者，看着周围一些同学CURD一把梭，不禁陷入了迷茫，开始怀疑自己在学校钻研了三年技术的意义。我常常想，难道我每天CURD就能解决问题了吗？&lt;/p&gt;
&lt;p&gt;纯粹的CRUD当然是解决不了问题的。随着工作的深入，我发现事情远没有我想象的那么简单。虽然是增删改查，但是在写增删改查的前提一定是对业务有相当程度的了解和熟悉。只有通过技术完成对真实业务的抽象和建模，才能写出来高扩展性的代码。而随着承接越来越多和越来越大的需求，我们所了解的业务知识也会慢慢变成自己的壁垒。在这个过程中，我们也会写出越来越健壮、可读性越来越高、扩展性越来越强的代码，我们掌握了对应业务知识，以及当前业务需要的性能和稳定性指标，都会成为我们宝贵的职业财富。&lt;/p&gt;
&lt;p&gt;业务技术团队和纯技术团队最大的不同是解决不一样的问题。业务团队负责解决真实世界的业务问题，纯技术团队则负责解决技术问题。某种程度上来说，科班出身的计算机同学更容易受到geek风的影响，认为毕业将来一定会做很多技术相关的工作，再加上面试过程中各种硬核问题，拉高了面试者的工作预期，才会导致大家投入工作后产生很大的割裂感。&lt;/p&gt;
&lt;p&gt;在业务团队，对于技术人来说，很大的忌讳就是硬秀肌肉，大家都想尝试新的技术，更高难度的技术挑战。但是，既然低并发能够解决业务问题，为什么还要处理高并发的各种问题自讨苦吃呢？尤其是在现在经营责任制的环境下，技术要做的就是服务业务，最小代价的解决业务的问题。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;其实处处留心皆学问，虽然很多硬核技术在日常的业务场景中用不到，但如果实在喜欢钻研技术，进入阿里后，还是有很多机会学习的。抽时间看中间件的源码、应用中间件的设计模式、解决日常遇到的各种诡异问题（maven排包、类加载）、线上问题的响应与处理，等等。只要抽出身来，即使在业务团队拧螺丝，但只要多花点时间准备，为业务造火箭的机会可能就会到来。&lt;/p&gt;
&lt;h2 id=&quot;草船借箭的勇气&quot;&gt;草船借箭的勇气&lt;/h2&gt;&lt;p&gt;写这篇文章的时候，通过GPT搜索了下，程序员中的I人会占到60%甚至更高，这种性格固然会使得大家在开发的时候更专注，但是往往也会导致开发同学在与人的沟通上不那么积极。&lt;/p&gt;
&lt;p&gt;很多人刚入职的时候，无论是学习业务，还是开发需求，在遇到问题的时候，都不好意思问师兄。甚至有些同学在师兄问项目进度的时候，支支吾吾地说快完成了，结果等到联调的时候才发现，自己写的代码和业务预期的代码，还是有相当大的出入的。&lt;/p&gt;
&lt;p&gt;所以，对于校招生来说，为了保证自己进步的速度，在合适的时机，多向周围的师兄、老板、业务方咨询问题，是一件非常有必要的事情。但是，大家往往缺少问问题的勇气，结合我自己和身边的例子，大概统计了下，一般有以下原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;和被咨询的人没打过交道不熟悉，不知道怎么开口；&lt;/li&gt;
&lt;li&gt;担心问题太简单，害怕被咨询的人嘲笑或者看低自己；&lt;/li&gt;
&lt;li&gt;大家都很忙，害怕影响到被咨询人的工作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;之前另外印象很深刻的一个例子，几年前，隔壁组一个刚入职的校招生在工位熬夜搞到11点，刚好我也在处理些事情，看他一直没走，就上前搭了几句话，得知他正在处理一个非常经典的Spring启动的问题。所以我就顺手帮他解决了。后来才知道，这个问题他已经看了快3个小时了。如果他能找机会向其他同学寻求帮助，那天晚上11点他就可以直接在床上原神启动了。&lt;/p&gt;
&lt;p&gt;不只是问别人，阿里有太多的技术资产了。遇到问题，搜索语雀、ata往往就有50%的概率解决问题。学会使用工具，也是成长的一部分。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;不像古代男耕女织、几乎一个家庭就能完成从生产到消费的全链路，现在的社会是以分工合作为基础的，所以我们必不可免的要和其他人打交道。甚至很多时候，我们自己的工作需要主动或者被动的依赖别人的帮助才能完成。&lt;/p&gt;
&lt;p&gt;新同学刚进入工作的时候，一定要多向师兄和同事借力。在新手保护期的阶段，大家会更容忍你有更多的“不知道”，所以，一定要在最懵懂的时候，尽可能的获取更多的知识。&lt;/p&gt;
&lt;p&gt;项目遇到卡点的时候，一定要多向老板借力。业务方工期紧？合作方不配合？这些涉及到更高层级沟通的地方，一定要想老板汇报，让老板站在更高的视角帮我们推动项目的执行。不过，在找老板之前，不能只把问题抛给老板，还有给出一定的解决方案和预期时间，这样在后续争取资源或者撕逼扯淡的时候，老板才会不至于那么被动。&lt;/p&gt;
&lt;p&gt;作为业务开发，业务的知识也是开发必须要掌握的基础之一。在遇到业务问题的时候，要多向业务借力。想业务咨询问题背景、可能的方案以及原因。多与业务沟通，不仅能掌握更多的业务知识，从功利的角度讲，也算是和业务方刷刷脸。大家更熟悉了，后续合作需求的gap也会越来越小，工作起来也会越来越顺心。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;草船借箭，不仅需要勇气，更需要智慧。聊之前准备好草船，也要注意时间和场合。完事之后表达感激也是必不可少的。至于是一句thanks，还是一杯咖啡，抑或是一顿饭，这个见仁见智吧～～～&lt;/p&gt;
&lt;h2 id=&quot;预期与项目管理&quot;&gt;预期与项目管理&lt;/h2&gt;&lt;p&gt;无论是写代码，还是PM技术项目，核心都是保障事情按时交付。在这个过程中，一定有自己不了解的地方，所以如何平衡好未知的坑和确定的交付时间，是我入职之后做技术PM最痛苦的点之一。&lt;/p&gt;
&lt;p&gt;如果遇到一个自己有超过30%都不熟悉的项目，建议此时一定要给自己多留一点buffer。这样做的原因有两点，一方面是面对未知的坑，运气好一点可以直接跳过去；运气差的话，直接陷进去一周都出不来。无论项目大小，作为项目负责人，我们不能让项目的交付时间跟随运气的好坏而波动。这样不仅会使得自己陷入被动，项目的对外放声，前线运营也会受到影响。&lt;/p&gt;
&lt;p&gt;给自己留buffer，不仅仅是帮助自己有更多时间踩坑，也是给自己留更多地和业务方聊排期的空间。如果一上来和业务聊了一个很紧张，加班后勉强才能做完的时间，后面这个项目大概率是要延期的。这么一搞，自己在合作方中的印象分就会很低。鲁迅大爷说过：“中国人的性情总是喜欢调和，折中的。譬如你说，这屋子太暗，须在这里开一个天窗，大家一定不允许的，但如果你主张拆掉屋顶，他们就回来调和，愿意开窗了。”所以，刚开始估一个留buffer的排期，后面到底是拆屋顶还是开天窗，就有很大的活动空间了。&lt;/p&gt;
&lt;p&gt;但是有的时候，项目的排期并不是我们这些小P能够决定的。930、330前要上线的项目一大堆，各个项目问就是P0、第N增长曲线，必须要做。没有留buffer的空间，这个时候该怎么办？&lt;/p&gt;
&lt;p&gt;过去的经验告诉我，在不断发现风险，解决风险的项目过程中，遇到难以解决的风险一定同步老板。及时让老板了解项目的进度，适时地管理项目参与方对项目的预期。这样才不至于在项目的各个里程碑中给各位大佬一个惊喜的SURPISE。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;印象中我刚入职半年后写了一篇技术PM的新人帖子，感兴趣的看官们可以拍砖：&lt;a href=&quot;https://wxxlamp.cn/2022/01/16/tech-pm-first/&quot;&gt;《技术PM初体验》&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;独乐乐不如众乐乐&quot;&gt;独乐乐不如众乐乐&lt;/h2&gt;&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;在阿里这三年，发现了太多大牛前辈，他们中的大部分人在我阿里的历程中都给予过非常重要的帮助。当我不解问及他们为何要在工作如此繁忙的时候还会抽出时间来给我答疑，他们的答案出奇的相似：&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;“因为当时淋过雨，才知道做这些无聊的事情有多痛苦”&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;“这些概念性的东西，知道后会少走很多弯路，没必要让你们把时间浪费在这些没有价值的事情上”&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;我听了之后甚是感动，在我看来，这是阿里师兄文化最好的体现。从实习到校招，我的每一任师兄和每一任老板，都在尽力帮我landing，教我了解技术原理、告诉我如何学习和成长、如何管理项目、如何控制风险，感谢这三年与他们相处的每一个瞬间，与他们相处的日子是我这辈子最宝贵的财富（之一，哈哈哈哈）&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;所以如果有时间，不妨多去帮助那些新来的朋友们，在他们成长起来之前，少一点PUA，多给予力所能及的帮助。这样自己开心，大家也高兴，无形之中团队的氛围就会慢慢变好，阿里的文化也会一点点的传承下去。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;其实帮助他们成长，对我们自己来说，又何尝不是一种成长？&lt;/font&gt;&lt;/p&gt;
&lt;h2 id=&quot;万物是否皆可卷&quot;&gt;万物是否皆可卷&lt;/h2&gt;&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;我是2020年入职阿里实习，以本科生身份校招转正的漏网之鱼。互联网公司在20、21、22这三年校招扩招的校招生非常多，站在现在的角度，以当下的招聘标准，作为一个普通本科生，我是无论如何也不会被招进来的。现在部门的校招生研究生起步、985打底，卷学历，作为开发，我已经输在了起跑线上。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;如果不能卷学历，那还能卷什么？卷加班时长？卷代码量？还是卷aone的工时？&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;我觉得这些都是一些过程指标，卷这些东西除了能够安慰和缓解下上次管理者的焦虑之外，是很难明显地解决其他问题的，对于自身的成长和提升更是没有一点帮助。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;互联网头部大厂作为人才的蓄水池，周围随处可见的都是优秀的人才，技术能力高，业务sense强，甚至每个人都有一套为人处事的方式方法。作为刚进入职场的菜鸟小白，我们应该努力卷我们自身的技能，向周围的大佬看齐，提升技术，学习业务，然后再将学习到的东西进行实践，从模仿到超越，实现学习、理解、实践、提升的闭环。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;在具备基础的技术和业务能力之后，作为业务开发，不要只盯着自己技术域的一亩三分地，而是慢慢地开始去卷业务，了解自己开发业务的背景、发展和规划，学着站在全局的地方去考虑问题。阿里对新人还是有容错的，校招生快速试错，然后不断成长，等待机会，承担更大的责任。之前翻到过张一鸣的一个演讲还是不错的：&lt;/font&gt;&lt;a href=&quot;https://xueqiu.com/2684655177/154256082&quot;&gt;张一鸣演讲整理&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;不妨慢一点，给自己一点时间，找到一两个自己在工作中和生活中真正想做的事情，然后坚持做下去。不要去卷各种无谓的指标，校招生，从卷自己开始。&lt;/font&gt;&lt;/p&gt;
&lt;h2 id=&quot;踩坑了，然后呢&quot;&gt;踩坑了，然后呢&lt;/h2&gt;&lt;p&gt;作为新人，在刚入职的时候，一定有很多不懂的地方，甚至有些同学之前没有使用过部门常用的开发语言，这都是很正常的事情。在刚来的一年时间里，大家总是会踩各种各样的坑，印象中，我踩了很多坑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;dts任务批量调用导致下游报警&lt;/li&gt;
&lt;li&gt;奥创发布上线不生效&lt;/li&gt;
&lt;li&gt;某些租户缺少配置导致NPE，等等&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;有些还导致了P5故障，在复盘的过程中，老板和师兄们帮我总结经验，让我收获了很多。很多人把这些当做工作履历的黑点不想面对，但是对于校招生来说，正相反，只要问题不大，都是改错的大好时机。俗话说，好记性不如烂笔头，像高中写错题笔记一样，将之前踩过的坑记录下来。一定会有一天，在第二次遇到类似的问题后，脑海中突然闪过第一次踩坑的场景，然后把记录的问题翻开，成功跳过这个坑，继续前行。&lt;/p&gt;
&lt;p&gt;大家对校招生的容错度是很高的，新同学一定要利用这个时间区间来试错和学习。记得我刚实习的时候，师兄给我review代码提了一些建议但是当时我没有放在心上，并没有改正，师兄很严厉的指出了我在回复comments过程中的问题，耐心教育我，“有人帮你CR指出错误，一定要有回应、讨论或者改正，这体现了对reviewer的重视。如果不回复的话，以后找相同人CR就很难获得高质量的comments”。这个建议我一直很受用，后续正式入职后，在师兄组织大面积CR的时候，在得到一些高质量的CR时候，我就会立刻提patch修复，其实，修复的越早，上线的风险也就越低。&lt;/p&gt;
&lt;p&gt;同时，踩坑之后，不仅要记得防止自己踩坑，如果能形成公开文档或者知识库，防止别人踩坑，或者是顺手把坑修复掉，这就更加功德无量了。（当然，如何让别人知道你把坑修复掉，这是一门艺术 //v//）&lt;/p&gt;
&lt;h2 id=&quot;听得懂、讲明白&quot;&gt;听得懂、讲明白&lt;/h2&gt;&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;作为程序员，虽然在日常的开发中与机器打交道的比较多，但是在项目管理的过程中，与人的沟通也是必不可免的。信息墒在传递的过程中一定是有损失的。所以，在交流的过程中，我们要通过各种方式来尽可能地保证信息的正确和完整传递。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;沟通是一个需要长期锻炼的技巧，有两个比较重要的点，一个是耐心，一个是细心。在听的时候，能够给伙伴足够多自我表达的空间，正常情况下，不要着急去否定和打断对方，正如你讲话时也不希望别人打断一样。此时我们需要做的就是细心去听，然后分条分点地把对方要表达的观点记录下来并消化。同时，在回答别人问题之前，可以用几句话把对方刚才的语言复述下来，尽量降低两个人沟通之间的GAP。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;同样的，在表达自己观点的时候，尽量也分条分点，像写作文一样，用总分总的方式，开篇和结尾表达问题关键点，中间分条分点论述。除此之外，一定要注意交代好上下文和背景。否则，很容易造成接受者的信息紊乱，形成驴头不对马嘴的尴尬场景。尤其是咨询问题的时候，如果直接问某一个具体的细节点，可能对方很难一下子get到意思，如果背景交代清楚，沟通起来就可能顺畅许多。&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font style=&quot;color:rgb(51, 51, 51);&quot;&gt;但是，一切的技巧终归是点缀，自己的影响力足够高，很多事情就迎刃而解了。&lt;/font&gt;&lt;/p&gt;
&lt;h2 id=&quot;拥抱变化&quot;&gt;拥抱变化&lt;/h2&gt;&lt;p&gt;拥抱变化作为新六脉，每一位同学都不陌生。在公司，拥抱变化不仅体现在口号上，更是在行动上表现的淋漓尽至。很多人每个财年甚至是每半个财年都要响应组织“拥抱变化”的号召，更换业务、团队和老板。在组织的调动下，遇见新的人，挑战新的事情。&lt;/p&gt;
&lt;p&gt;作为校招生，刚开始对变化这两个词其实感知是不深刻的。第一次感知到变化的时候，是我入职5个月后整个组被通知换到了另一个BU，因为只是换了组织关系，周围合作的同学和老板没有变化，所以对于这次的变化只是有一个感性的认识。第二次感知到变化的时候，是22年从实习就开始带我的老板毕业之后，整个团队都陷入了一场漩涡之中，那次我就陷入了思考。第三次感知到变化，则是我自己的选择，选择了换个环境继续工作。&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;我们一定要承认一个事实，就是业务离开谁都可以转。大多数开发都没有那种技术能力强到令人望尘莫及的地步，刚开始我还会天真地想，这个大佬这么懂这块技术，他变化了业务可怎么办？后来工作时间久了慢慢发现，人变了，事情才有可能发生变化，没有需求是加代码、加人日、上重构解决不了的。虽然所谓的重构有可能也是把之前的feature又实现了一遍，你就说能不能用吧，哈哈哈。&lt;/p&gt;
&lt;p&gt;学习的能力很重要，在面对变化的时候，我们要学会迅速融入环境，这是一件非常重要的事情。学会快速学习业务，快速接手新的代码逻辑，整理一套自己的方法论。要知道，再也没有校招入职时候连给2个月熟悉代码的快乐时光了，如何在不熟悉的代码中交付业务需求、不出现风险，甚至还能保证架构的统一，是作为程序员，在我司的大环境下拥抱变化的基础能力。&lt;/p&gt;
&lt;p&gt;在变化中，我们要知道 —— 不管周围的人或事再怎么变化，不变的始终是自己。所以提高自己的能力，借事修人，才是变化中不变的底色。&lt;/p&gt;
&lt;h2 id=&quot;高P和低P&quot;&gt;高P和低P&lt;/h2&gt;&lt;p&gt;工作中遇到过很多P9，基本能力都很强，沟通起来有种如沐春风的感觉。但和他们不能深聊，聊着聊着，就发现好多人都喜欢对自己不太了解的领域大放厥词。一个是某HR，认为技术岗清北的研究生比不上UCL和KCL的硕士，另一个某销售leader，在锐评deepseek的时候，说了很多常识性错误。所以作为校招生，不要莫名地就给高P加滤镜，还是那句话：“出门在外，身份是公司给的”，他们究竟有几把刷子，其实还真不好说。最起码，在你最熟悉的领域，他们可能真的没你懂。&lt;/p&gt;
&lt;p&gt;其实另外想表达的就是，现在的很多高P，都是在以前的互联网红利期上升起来的，可能他们自己的知识储备和学习能力已经跟不上时代的发展了。很多校招生还想copy以前的路子，来了之后就想晋升，幻想着自己两年六两年七三年八，踩狗屎运后还能升个9。实话讲，概率很小。无论你怎么奋斗，可能最后的结果都会离你的预期偏差很远，因为晋升不止是看实力，运气也很重要。我简单列举几个场外因素，是否经常换团队、是否遇到好老板、是否有晋升坑位、业务是否在上升期、是否年龄合适等等。&lt;/p&gt;
&lt;p&gt;虽然说，不想当将军的士兵不是好士兵，但是公司大了之后组织病就会随之而来，甚至自己也会因为高层的政策而无故躺枪。所以，有时候即使你再努力，可能也不会拿到如意的结果，这其实都是很正常的。我见过很多业务能力和技术能力都很强的人，因为各种因素没有晋升成功。俗话说，时代的一粒灰，落在人身上就是一座山。放平心态，抛弃title，从本质去对待公司的每一个人。&lt;/p&gt;
&lt;h2 id=&quot;接雨水和修屋顶&quot;&gt;接雨水和修屋顶&lt;/h2&gt;&lt;p&gt;虽然说并不是啥都能卷，但是在当下这个卷的环境下，很多时候我们会不得不陷入内卷的困境里，麻木地做着一成不变的事情。刚进入工作时候的那种激情和勇敢，就会在这种不断承接被动需求的环境下慢慢地被消磨殆尽。&lt;/p&gt;
&lt;p&gt;甚至有时候，我们可能会因为同事的负面看法而每天患得患失；可能会因为工作中的一点小疏漏而彻夜辗转反侧；可能会因为项目未完成而闷闷不乐，封闭自我。“他人即地狱”，工作久了之后，我们往往会陷入周围的评价体系里面，成为他人语言的奴隶，而不再是自己的主人。这个时候，向外求索，可能会是一种解法。&lt;/p&gt;
&lt;p&gt;公司的光环和个人的能力是有区别的。不妨在公司内部的评价标准上，尝试通过外部的评价来更确切地定位自己。空闲时多接接雨水，未雨绸缪，方才能让自己在暴雨如注之时独善其身。现在回想起来2022年的大裁员风波，依然是令人两股战战，不寒而栗。&lt;/p&gt;
&lt;p&gt;未雨绸缪，晴天修屋顶也是十分必要的。在需求少的时候，身体上放松，精神上重视。多了解对手公司的竞品动态，反思自己在工作中的优与劣。时刻掌握本领域业界的趋势，反哺自己领域的业务和技术。用积极的态度，为自己建立一个更加全面的评价体系，清晰的定位自己，进而针对性提升。说句题外话，现在大模型这么火，公司的每个部门都希望通过大模型来缓解眼前问题，画大饼、讲新故事，如果作为大头兵的我们还不了解这些东西，一味的curd，看似是处在舒适区，其实是自己把自己推向了深渊。雪崩的时候，没有一篇雪花是无辜的。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;海阔凭鱼跃，天高任鸟飞。&lt;/p&gt;
&lt;p&gt;无论如何，这也只是一份工作，因为工作导致抑郁，实在得不偿失。珍惜身边的每一位朋友，尝试与自己和解，接受自己做不到的，争取自己能得到的，坦然面对结果，做人生的朋友。&lt;/p&gt;
&lt;p&gt;山穷水复疑无路，柳暗花明又一村。&lt;/p&gt;
&lt;h2 id=&quot;新的开始&quot;&gt;新的开始&lt;/h2&gt;&lt;p&gt;本来想在三周年之际发表这篇文章，没想到中间经历了一系列的事情，这一耽搁就是半年。不过虽然拖拉了这么久，但是好在发布出来了:)&lt;/p&gt;
&lt;p&gt;正好要于2月17号参加公司的三年醇活动。悟以往之不谏，知来者之可追。以此，来祭奠我的阿里三周年，迎接在阿里的新挑战^_^&lt;/p&gt;
</content:encoded></item>
<item><title>分布式锁的实现与并发测试</title><link>https://wxxlamp.cn/2024/06/15/distributed-lock-implementation/</link><guid isPermaLink="true">https://wxxlamp.cn/2024/06/15/distributed-lock-implementation/</guid><pubDate>Sat, 15 Jun 2024 10:39:00 GMT</pubDate><description>本文详细介绍分布式锁的实现要点，涵盖可重试、可续期、加锁释放锁一致性等关键设计，包含完整的加锁业务逻辑和Redis连接异常处理策略，并提供Java实现代码示例。</description><content:encoded>&lt;h1 id=&quot;制作锁&quot;&gt;制作锁&lt;/h1&gt;&lt;p&gt;编写分布式锁的注意点&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;注意锁的可重试、可续期、以及加锁和释放锁的一致&lt;/li&gt;
&lt;li&gt;如果连接不上redis如何处理？如果线程抢占锁失败如何处理？&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;加锁业务逻辑&quot;&gt;加锁业务逻辑&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;包含业务要求的重试、以及redis连接不上、线程抢占锁失败的处理&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;48&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &amp;lt;T, R&amp;gt; R &lt;span class=&quot;title function_&quot;&gt;runWithLock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Function&amp;lt;T, R&amp;gt; function, T req, Class&amp;lt;? extends AssuranceSyncHandler&amp;lt;T,R&amp;gt;&amp;gt; clazz,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;                                       &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; expiredSec, String... factors)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AssuranceSyncHandler&amp;lt;T, R&amp;gt; zedSynchronizedHandler = getSyncHandlerFromClazz(clazz);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;ZedBody&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;zedBody&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; buildBody(req, expiredSec, factors);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 尝试加锁&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;lockSuc&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; tryLock(zedBody, zedSynchronizedHandler);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 如果没加锁成功，则按照配置策略进行重试&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (!lockSuc &amp;amp;&amp;amp; !retryLock(zedBody, zedSynchronizedHandler)) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        LOGGER.info(&lt;span class=&quot;string&quot;&gt;&quot;thread can not get lock, retry times is {}&quot;&lt;/span&gt;, zedSynchronizedHandler.getRetryTimes());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// 如果缓存不可用，则直接执行业务逻辑&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (SmartZedThreadLocal.isSynchronizedLockInvalid()) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            LOGGER.error(&lt;span class=&quot;string&quot;&gt;&quot;lock failed because of the system error, so func apply directly&quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            SmartZedThreadLocal.clear(zedBody.getRowKey());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; function.apply(req);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// 如果缓存可用，则执行自定义锁策略&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; zedSynchronizedHandler.strategyWhenSync(req);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    R result;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        result = function.apply(req);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Exception ex) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        LOGGER.error(&lt;span class=&quot;string&quot;&gt;&quot;AssuranceLockUtils.runWithLock error when apply func&quot;&lt;/span&gt;, ex);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;throw&lt;/span&gt; ex;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    } &lt;span class=&quot;keyword&quot;&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        tryUnLock(zedBody);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; result;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@SuppressWarnings(&quot;unchecked&quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &amp;lt;T, R&amp;gt; AssuranceSyncHandler&amp;lt;T, R&amp;gt; &lt;span class=&quot;title function_&quot;&gt;getSyncHandlerFromClazz&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Class&amp;lt;? extends AssuranceSyncHandler&amp;lt;T,R&amp;gt;&amp;gt; clazz)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; Optional.ofNullable(clazz)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            .map(ApplicationContextUtil::getBean)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;comment&quot;&gt;// 此处加map是为了把子类转成父类&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            .map(e -&amp;gt; (AssuranceSyncHandler&amp;lt;T, R&amp;gt;)e)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            .orElse((AssuranceSyncHandler&amp;lt;T, R&amp;gt;)defaultHandler);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;retryLock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(ZedBody zedBody, AssuranceSyncHandler&amp;lt;?,?&amp;gt; handler)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;retryTimes&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;lockSuc&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt;(!lockSuc &amp;amp;&amp;amp; handler.getRetryTimes() &amp;gt; retryTimes++) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        lockSuc = tryLock(zedBody, handler);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; lockSuc;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;内部锁加锁逻辑&quot;&gt;内部锁加锁逻辑&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;内部锁的粒度要控制，&lt;strong&gt;以及如果两个锁释放失败如何处理&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;感觉也没必要加内部锁，因为如果并发度不高的话，每次都会获取到redis锁，再加上内部锁。反而降低了性能。如果单机并发很高，可以考虑加内部锁&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(ZedBody zedBody, AssuranceSyncHandler&amp;lt;?,?&amp;gt; zedSynchronizedHandler)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;ans&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;innerTryLock&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        innerTryLock = LocalLockHolder.tryLock(zedBody.getRowKey(), zedSynchronizedHandler.getRetryInterval());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (innerTryLock) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            ans = assuranceSyncService.lock(zedBody);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Throwable e) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        LOGGER.error(&lt;span class=&quot;string&quot;&gt;&quot;AssuranceLockUtils.runWithLock error&quot;&lt;/span&gt;, e);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    } &lt;span class=&quot;keyword&quot;&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// 如果外部锁没加成功，则释放内部锁&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (!ans &amp;amp;&amp;amp; innerTryLock) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            LocalLockHolder.unlock(zedBody.getRowKey());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; ans;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;tryUnLock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(ZedBody zedBody)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        LocalLockHolder.unlock(zedBody.getRowKey());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        assuranceSyncService.unLock(zedBody);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Throwable e) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        LOGGER.error(&lt;span class=&quot;string&quot;&gt;&quot;AssuranceLockUtils.runWithUnLock error&quot;&lt;/span&gt;, e);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LocalLockHolder&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Map&amp;lt;String, Lock&amp;gt; LOCK_MAP = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ConcurrentHashMap&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String key, &lt;span class=&quot;type&quot;&gt;long&lt;/span&gt; timeout)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; InterruptedException {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;type&quot;&gt;Lock&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; LOCK_MAP.computeIfAbsent(key, k -&amp;gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ReentrantLock&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; lock.tryLock(timeout, TimeUnit.MILLISECONDS);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String key)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;type&quot;&gt;Lock&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; LOCK_MAP.get(key);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (lock != &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            lock.unlock();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        LOCK_MAP.remove(key);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;核心加锁逻辑&quot;&gt;核心加锁逻辑&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;包含锁的可重入处理&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;48&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;49&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;50&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;51&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;52&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;53&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;54&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;55&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;56&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;57&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;58&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;59&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;60&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;61&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;62&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;63&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;64&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;65&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;66&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;67&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;68&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;69&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;70&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;71&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;72&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;73&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;74&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;75&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;76&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;77&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;78&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;79&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;80&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;81&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;82&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;83&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;84&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;85&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;86&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;87&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;88&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;89&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;90&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;91&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;92&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;93&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; Boolean &lt;span class=&quot;title function_&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(ZedBody zedBody)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 1. 竞争锁&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;suc&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; syncService.set(zedBody.getRowKey(), getValue(), zedBody.getTimeout(), () -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// 如果redis没有连接上，则不加锁&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        SmartZedThreadLocal.markSynchronizedLockInvalid(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    });&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (suc || isOwnLock(zedBody)) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// 增加引用次数&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; increaseCount(zedBody.getRowKey());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        LOGGER.info(&lt;span class=&quot;string&quot;&gt;&quot;lock suc key:{},threadId:{}, count {}&quot;&lt;/span&gt;, zedBody.getRowKey(), Thread.currentThread().getId(), count);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; Boolean &lt;span class=&quot;title function_&quot;&gt;unLock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(ZedBody zedBody)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;lockCount&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; threadLocal.get().getByKeyWithDefault(zedBody.getRowKey()).count.get();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    LOGGER.info(&lt;span class=&quot;string&quot;&gt;&quot;un lock start key:{},threadId:{},count:{}&quot;&lt;/span&gt;, zedBody.getRowKey(),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            Thread.currentThread().getId(), lockCount);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 1. 如果引用计数已经小于0，则失效缓存&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (decreaseCount(zedBody.getRowKey()) &amp;lt;= &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; syncService.del(zedBody.getRowKey());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;increaseCount&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String key)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;LockCounterHolder&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;counterHolder&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; threadLocal.get();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; counterHolder.increaseWithKey(key);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;decreaseCount&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String key)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;LockCounterHolder&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;counterHolder&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; threadLocal.get();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; counterHolder.decreaseWithKey(key);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;isOwnLock&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(ZedBody zedBody)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; syncService.get(zedBody.getRowKey());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; StringUtils.equals(s, getValue());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; String &lt;span class=&quot;title function_&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; LOCAL_HOSTNAME + &lt;span class=&quot;string&quot;&gt;&quot;:&quot;&lt;/span&gt; + Thread.currentThread().getId();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; String &lt;span class=&quot;title function_&quot;&gt;getHostname&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; InetAddress.getLocalHost().getHostName();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (UnknownHostException e) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        LOGGER.warn(&lt;span class=&quot;string&quot;&gt;&quot;Failed to get hostname&quot;&lt;/span&gt;, e);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;[unknown]&quot;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LockCounterHolder&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Map&amp;lt;String, LockCounter&amp;gt; counters = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;HashMap&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; LockCounter &lt;span class=&quot;title function_&quot;&gt;getByKeyWithDefault&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String key)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; counters.computeIfAbsent(key, e -&amp;gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LockCounter&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;increaseWithKey&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String key)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;type&quot;&gt;LockCounter&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; getByKeyWithDefault(key);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; counter.count.incrementAndGet();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;decreaseWithKey&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String key)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;type&quot;&gt;LockCounter&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; counters.get(key);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// 防止非持有锁的线程释放锁&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (counter == &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ZedSynchronizedException&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&quot;key is not found when release. key=&quot;&lt;/span&gt; + key);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; counter.count.decrementAndGet();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (count &amp;lt;= &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;comment&quot;&gt;//如果count 小于0，删除key，避免内存泄漏&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            counters.remove(key);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; count;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LockCounter&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; AtomicInteger count;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;LockCounter&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.count = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AtomicInteger&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Redis连接逻辑&quot;&gt;Redis连接逻辑&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;包含redis连接不上的重试&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String key, String value, &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; expireTimeSec)&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;connectTime&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;SetParams&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;setParams&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;SetParams&lt;/span&gt;().nx().ex(expireTimeSec);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; (Objects.isNull(v)) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; (&lt;span class=&quot;type&quot;&gt;Jedis&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;jedis&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; jedisPool.getResource()) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            v = jedis.set(PREFIX + key, value, setParams);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; StringUtils.equals(SUCCESS, v);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Exception e) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            LOGGER.error(&lt;span class=&quot;string&quot;&gt;&quot;rdb3 setNx error key:{} value:{}&quot;&lt;/span&gt;, key, value, e);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (connectTime++ &amp;gt;= RECONNECT_TIMES) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                LOGGER.error(&lt;span class=&quot;string&quot;&gt;&quot;rdb3 retry {} times setNx error key:{} value:{}&quot;&lt;/span&gt;, RECONNECT_TIMES, key, value, e);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;throw&lt;/span&gt; e;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; StringUtils.equals(SUCCESS, v);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h1 id=&quot;测试锁&quot;&gt;测试锁&lt;/h1&gt;&lt;p&gt;基于上面的分布式锁，当我想在单机上通过UT来保证锁的正确性，该如何写UT呢？&lt;/p&gt;
&lt;h2 id=&quot;第一版&quot;&gt;第一版&lt;/h2&gt;&lt;p&gt;我写的第一版测试代码如下：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;testLockSync&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; InterruptedException {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 1. 第一个线程抢占到锁，睡眠&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; AssuranceLockUtils.runWithLock((req) -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                Thread.sleep(&lt;span class=&quot;number&quot;&gt;1000&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                e.printStackTrace();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }, &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        Assert.assertEqual(&lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;, res);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }).start();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 2. 第二个线程尝试抢占锁，应该失败&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            AssuranceLockUtils.runWithLock((req) -&amp;gt; &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;, &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Exception exception) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            Assert.assertTrue(exception instanceOf ZedSynchronizedException);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }).start();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;眼尖的同学可能一眼就看出来了程序的问题，主线程无法感知到测试结果。如果要保证主线程感知到测试结果，有两个需要注意的点，第一就是需要把测试线程的值传递给主线程。第二个点就是要保证主线程比测试线程执行的晚。常见的方式有 &lt;code&gt;CountDownLatch&lt;/code&gt;、&lt;code&gt;BlockQueue&lt;/code&gt;和共享内存等&lt;/p&gt;
&lt;h2 id=&quot;第二版&quot;&gt;第二版&lt;/h2&gt;&lt;p&gt;所以我尝试写了第二版测试代码：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;testLockSync&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; InterruptedException {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;CountDownLatch&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;latch&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CountDownLatch&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AtomicReference&amp;lt;String&amp;gt; firstThreadAns = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AtomicReference&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AtomicReference&amp;lt;Exception&amp;gt; secondThreadAns = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AtomicReference&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 1. 第一个线程抢占到锁，睡眠&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        firstThreadAns.set(AssuranceLockUtils.runWithLock((req) -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                Thread.sleep(&lt;span class=&quot;number&quot;&gt;1000&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                e.printStackTrace();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            } &lt;span class=&quot;keyword&quot;&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                latch.countDown();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }, &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }).start();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 2. 第二个线程尝试抢占锁，应该失败&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            AssuranceLockUtils.runWithLock((req) -&amp;gt; &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;, &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Exception exception) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            secondThreadAns.set(exception);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        } &lt;span class=&quot;keyword&quot;&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            latch.countDown();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }).start();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 3. 主线程check结果&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    latch.await();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Assert.assertEquals(&lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;, firstThreadAns.get());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Assert.assertTrue(secondThreadAns.get() &lt;span class=&quot;keyword&quot;&gt;instanceof&lt;/span&gt; ZedSynchronizedException);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;执行这个测试程序后，会有下面几个异常的case：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;31行的断言失败，firstThreadAns.get() == null&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/dbcd4d8e_8e094577.png&quot;&gt;原因是因为14行，测试线程提前执行&lt;code&gt;latch.countDown()&lt;/code&gt;，导致测试线程没有把测试结果传递给主线程，主线程就开始check结果&lt;/li&gt;
&lt;li&gt;主线程一直等待，导致程序不能结束&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/f9929e9f_9b8f3bf1.png&quot;&gt;原因是因为第二个线程先执行，第一个线程没有获得到锁，抛出加锁失败异常，导致没有执行&lt;code&gt;latch.countDown()&lt;/code&gt;方法，进行会导致主线程一直pending。解决方案很简单，就是保证第一个测试线程先于第二个测试线程执行。可以使用&lt;code&gt;Semaphore&lt;/code&gt;、&lt;code&gt;CountDownLatch&lt;/code&gt;和锁，但是这里要注意，不能用&lt;code&gt;Thread.join()&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;第三版&quot;&gt;第三版&lt;/h2&gt;&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;testLockSync&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; InterruptedException {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;CountDownLatch&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;latch&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CountDownLatch&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;2&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;Semaphore&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;semaphore&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Semaphore&lt;/span&gt;(&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AtomicReference&amp;lt;String&amp;gt; firstThreadAns = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AtomicReference&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AtomicReference&amp;lt;Exception&amp;gt; secondThreadAns = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AtomicReference&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 1. 第一个线程抢占到锁，睡眠&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        firstThreadAns.set(AssuranceLockUtils.runWithLock((req) -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            req.release();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                Thread.sleep(&lt;span class=&quot;number&quot;&gt;1000&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (InterruptedException e) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                e.printStackTrace();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }, semaphore, &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        latch.countDown();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }).start();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 2. 第二个线程尝试抢占锁，应该失败&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Thread&lt;/span&gt;(() -&amp;gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            semaphore.acquire();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            AssuranceLockUtils.runWithLock((req) -&amp;gt; &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;, &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;, &lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        } &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Exception exception) {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            secondThreadAns.set(exception);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        } &lt;span class=&quot;keyword&quot;&gt;finally&lt;/span&gt; {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            latch.countDown();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        }&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    }).start();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 3. 主线程check结果&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    latch.await();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Assert.assertEquals(&lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;, firstThreadAns.get());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Assert.assertTrue(secondThreadAns.get() &lt;span class=&quot;keyword&quot;&gt;instanceof&lt;/span&gt; ZedSynchronizedException);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/wxxlamp/blog-img-repo@main/images/6cc2037a_ee446bac.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;完美&lt;/p&gt;
</content:encoded></item>
<item><title>2023 年度回顾：职业选择与自我审视</title><link>https://wxxlamp.cn/2024/01/22/my-2023/</link><guid isPermaLink="true">https://wxxlamp.cn/2024/01/22/my-2023/</guid><pubDate>Mon, 22 Jan 2024 12:19:00 GMT</pubDate><description>2023年终总结：未能晋升后豁然开朗，备战跳槽收获字节与拼多多offer却选择留下。与Hollis合作的Java八股上线，三战雅思考过6.5分。下半年投身新方向，完成了从内卷到审视自我需要什么的转变。</description><content:encoded>&lt;p&gt;此刻坐在返乡的火车上，看着窗外鹅毛铺成的雪地，在欣赏着无垠而又空旷的雪白的同事，我也又陷入了回忆。&lt;/p&gt;
&lt;p&gt;会想起去年，如果让我用一个字来描述我的2023，就是累，用两个字描述就是充实。除此之外，如果再想几个关键词的话，就是平和、有意思、突破自我、但也不够坚持。虽然过的也跌宕起伏，不过总的来说，一年有一年的成长，一年有一年的惊喜。&lt;/p&gt;
&lt;h3 id=&quot;2023打脸报告&quot;&gt;2023打脸报告&lt;/h3&gt;&lt;p&gt;2023年，对我来说更是里程碑式的一年（连续三年都是里程碑了，明年争取换个词）。按照惯例，先看下2023年的flag的完成度怎么样：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个好身体：累计跑步1800KM，做俯卧撑和仰卧起坐 =============&amp;gt; 100%，跑步达到2000KM，俯卧撑基本每天都做，仰卧起坐没做，但是经常做引体向上，总体来说完成任务。&lt;/li&gt;
&lt;li&gt;Java八股面世，为有需要的人提供帮助 =============&amp;gt; 70%，八股面试，帮助了很多人。不过后期因为自己面试和学习英语，便退出了八股的编辑。&lt;/li&gt;
&lt;li&gt;继续尝试自媒体，B站抖音发布6个视频，以及文章类平台输出 =============&amp;gt; 10%，只发了2个视频，自媒体进度几乎为0。这玩意没有坚持的决心是做不下来的。&lt;/li&gt;
&lt;li&gt;做点有意思的事情，鬼畜&amp;amp;采访 =============&amp;gt; 0%，下半年有时间的时候完全忘了这个flag…..&lt;/li&gt;
&lt;li&gt;去年在计划中有一个缩写是zx，说了今年要公布，zx其实就是涨薪哈哈 =============&amp;gt; 100%，因为去年绩效还不错，所以顺利涨薪。&lt;/li&gt;
&lt;li&gt;提升专业能力，博客12篇&amp;amp;看4本书 =============&amp;gt; 50%，博客倒是有12篇，但是技术相关的只有80%。至于看书，今年看了置身事内还有余华的兄弟，而且因为太悲哀了，所以还没有看完。印象中上次看应该是22年，看了刘震云的一地鸡毛和一句顶一万句。多说一句，刘震云虽然写小说比较吊，但是之前他在脱口秀大会做嘉宾的表现，我不是很喜欢。&lt;/li&gt;
&lt;li&gt;学习英语，争取通过一些考试 =============&amp;gt; 100%，因为工作中对英语的需要，再加上大学没过英语六级的遗憾，所以特意花了三个月的时间考了雅思，最终考了6.5的成绩，虽然不高，但是对于高考没考过听力、口语的我来说，已经知足了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;2023复盘总结&quot;&gt;2023复盘总结&lt;/h3&gt;&lt;p&gt;站在新的一年来回顾去年，感觉快，但是也感觉充实。虽然充实，但是说实话，前半年的事情，有一点点遗忘了。容我想想。&lt;/p&gt;
&lt;p&gt;不同于2024年2月10日的春节，2023年1月22日的春节是21世纪第二早的春节。在一月份年前就是作为金融域的大促PM负责网站三月大促的项目管理和技术开发工作。过年在家里也没有闲着，每天都是早早起床，忙家里生意，拜年。以及年后在工作第一天也就是大年初七我带着爸去市医院治疗他的呼噜，动了手术。也正是那几天，才让我知道了在小城市，没有关系寸步难行的真正含义，也正是那几天，让我明白，像我这样的小镇做题家，只靠努力三年的在别人眼里还不错的大学，如何能在小城市这些其他奋斗几代人的关系网中生存呢？所以，今后的日子，我只能背井离乡，远离我的故土。不是不想回，只是回不去啊。&lt;/p&gt;
&lt;p&gt;年后回公司就是二月了，二月和三月因为财年底，所以二月不仅在做三月大促最后的冲刺，也在加班搞述职review。因为互联网大环境不好，公司还在降本增效，虽然我早已预料到今年我不能晋升，但是在得知晋升名额没有我的时候，还是有点小伤心的。轮项目管理能力，我刚毕业就作为技术PM负责金融侧200人日左右的项目，这其中的辛苦只有自己知道；轮业务能力，在去年业务架构和骨干开发纷纷跑路的情况下，是我独自扛起了业务需求，并且带新同学慢慢了解和完成项目；轮技术能力，我开发了不需要写一行前端代码的、基于Java注解的配置化页面的 运营平台，且从0-1对某核心系统进行迁移、领域重构和UT建设。我自问我已经符合晋升的标准，但是我也知道，从公司的角度出发，晋升并不是对能力的肯定，而是和薪酬一样，是一种管理方式。对于公司来讲，我用最小的成本，招到最合适的人即可，如果我断定我不给你晋升，你也不会跑路，那我为什么要给你晋升呢？而且小P晋升一般都是排队，我前面还有一个排队三年的兄弟，我又是中途转BU的，所以从老板视角来看，不晋升我倒也正常。所以对于我来讲，看这件事情，我也就平常心对之了。不过老板却是对我还可以，除了没晋升，绩效倒是不错。&lt;/p&gt;
&lt;p&gt;财年结束，按照公司一贯的尿性，又要开始换团队了，而我，又是那个被拆分的幸运儿，被换到了一个新老板下面。我之前带的小师妹则是跟着原来的老板去了其他团队。久经变化的我对这些拆分已经无感，毕竟在上层看来，无论是员工汰换，还是组织变化，都是为了人力资源更好的利用和配置，在他们眼里，我们哪里能算上“人”呢？不过新老板也不错，他还是阿里Java开发规范的作者之一，不得不说，这一年跟他学到了不少优秀的编码技巧。&lt;/p&gt;
&lt;p&gt;因为没有晋升名额，我突然一下子变得释然了。我的思想和目标发生了转变，不再像前两年一样，为了虚无的晋升疯狂卷，我真正开始审视自己需要什么，以及离开公司，我究竟是什么？所以从4月开始，我便开始准备面试和跳槽。除此之外，3月和四月我还在和Hollis一起完善Java八股，应该是4月底，Java八股成功上线，距今累计帮助近万名程序员。虽然Java八股我也获得了部分物质上的收益，但是对于我来讲，他更是一种成就感，因为这是我从刚进公司就想搞的事情，终于在Hollis的帮助下做成了。&lt;/p&gt;
&lt;p&gt;五月就是准备面试以及参加各种面试，基本六月的时候就开始收割offer，最后比较满意的是拿到了字节的支付和拼多多营销团队的offer，但是因为感觉太累以及一些其他目前不太方便透露的因素，我最终还是选择留在了原来的公司。但是留下来，是经过我深思熟虑的，而我留下来后，也不会再为了晋升这些虚无的名额疯狂内卷，而是为了一些自己真正想做的事情。&lt;/p&gt;
&lt;p&gt;确定还留在原来公司后，七月份便开始思考自己离开公司后如何证明自己的价值，因为比较忙，为此我还退出了Java八股项目的编辑工作，这意味着从感情上，我要和我之前的知识库进行切割，从物质上，我也无法再靠它盈利。但是权衡利弊后，我还是选择了投身进入新的事情当中，因为我知道，做了才不后悔。同时，在工作中，七月份，我也离开了原来的工作岗位，从新负责一块新的领域，这对我来说，是机会，也是挑战。七月份还有一个标志性事件，就是入职公司两周年，想不到，一转眼这么快了。&lt;/p&gt;
&lt;p&gt;因为工作和自己大学六级没过的遗憾，所以八月份便开始准备雅思考试，用百次斩陆陆续续背了背单词。到了九月份，感觉只背单词确实不行，所以就逼着自己花了2K报了名，果然有了deadline之后，学习计划，学习效率徒增。国庆也没有出去玩，在公司会议室每天阅读、听力、作文连续搞了8天。因为河南考试高考不考口语和听力，所以我第一次口语和听力考的很差，口语只有5。有点不甘心，报了12月初的第二次考试，结果不知道是天意弄人还是咋地，第二次的口语题和第一次一样，但是我大意了以为不会再考就没有准备，而且考试成绩和第一次竟然一模一样。我觉得我要突破一下，我对自己说，如果我能在工作的情况下，以河南考生的身份把雅思考过6.5的话，我将来不管做什么事情都有底气。反而我知道，如果我考不过的话，那将是对我自信的巨大打击，所以我又报考了12月末的第三次，这次皇天不负有心人，终于考过到了6.5，虽然听力依然拉胯，但是阅读竟然有8分，可能这就是刷题党吧（PS前两次的阅读是7.5）。虽然刚开始并没有想一定要把雅思考过，但是报考后劲就上来了，它不仅是一个考试，考验的是我工作两年后的专注度，执行力，也算是对我离开学校两年后学习能力的一种检测吧。总体来说，结果还是令我满意的，也能继续让我普信下去。&lt;/p&gt;
&lt;h3 id=&quot;2024FLAG&quot;&gt;2024FLAG&lt;/h3&gt;&lt;p&gt;假如说2023刚开年的时候我隐约找到了一些方向，但是在按照既定的规划向前出发的时候，我又有些许的迷茫。在2024年，又是我一个重新寻找方向一年。但是也不必悲伤，因为人生本来就是兜兜转转，来来回回。24年的计划，我会分为健康、学习、副业三块：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;健康方面，每年都是重复的，一个是运动，一个是作息，去年作息不是很好，希望今年能纠正一下：&lt;ol&gt;
&lt;li&gt;稳定的作息：8点半起床，晚上12点睡觉。工作日无特殊情况至少1个半小时在学习和写作上，周末无特殊情况至少6个小时在学习和写作上；&lt;/li&gt;
&lt;li&gt;继续运动：每周6次引体+卷腹，每次2-3组，年底最少4块腹肌。全年跑步路程800KM&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;学习方面，去年下半年几乎没有提升专业能力，新的一年要再提升点关于系统并发和领域建模的知识，除此之外，要养成持续读书的好习惯&lt;ol&gt;
&lt;li&gt;完善高并发场景热点系统的处理体系；12篇技术博客&lt;/li&gt;
&lt;li&gt;学习IOS和Vision pro开发（待定）&lt;/li&gt;
&lt;li&gt;对国内金融有基础的了解&lt;/li&gt;
&lt;li&gt;阅读2本小说（待定）&lt;/li&gt;
&lt;li&gt;学习基础的AI知识，能够利用大模型进行基础的数据建模&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;副业方面，建立以知识创作和个人思考的自媒体平台，本年度全平台粉丝1000人。知识创作的前提是个人/IP有知识才可以，所以核心还是在学习和思考上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2024年，一定是人生转折的一年。加油！&lt;/p&gt;
</content:encoded></item>
</channel></rss>