<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Folay&apos;s Blog</title><description>Android Developer</description><link>https://folay.top</link><language>zh-CN</language><follow_challenge><feedId>166915344625814528</feedId><userId>66421915972268032</userId></follow_challenge><item><title>LLM 不会数数——输出长度控制的工程实践</title><link>https://folay.top/zh/blog/llm-length-control</link><guid isPermaLink="true">https://folay.top/zh/blog/llm-length-control</guid><description>36 次控制变量实验揭示：计数口径偏差、推理 token 挤压、小样本幻觉，三个坑叠在一起导致 93% 的输出超标。</description><pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;36 次控制变量实验揭示：计数口径偏差、推理 token 挤压、小样本幻觉，三个坑叠在一起导致 93% 的输出超标。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你让 LLM “写 2500 个字”。它不会数。&lt;/p&gt;
&lt;p&gt;不是 prompt 没写对，不是参数没调好。自回归架构每一步只预测下一个 token，没有内部计数器。模型对长度的“感知”来自训练数据的分布模式，不是精确计算。&lt;/p&gt;
&lt;p&gt;这个结论我花了两周、36 次控制变量的 API 调用才真正接受。&lt;/p&gt;
&lt;h3&gt;一、93% 的输出超标&lt;/h3&gt;
&lt;p&gt;在一个中文长文本批量生成管线中，每次调用要求模型输出约 2500 个中文汉字（带结构化大纲和上下文衔接）。系统跑了一段时间后，审计了 1200+ 个输出：&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;th&gt;平均超出&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;2500&lt;/td&gt;&lt;td&gt;331&lt;/td&gt;&lt;td&gt;&lt;strong&gt;93%&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;+1685 字&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2900&lt;/td&gt;&lt;td&gt;526&lt;/td&gt;&lt;td&gt;31%&lt;/td&gt;&lt;td&gt;+420 字&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3000&lt;/td&gt;&lt;td&gt;356&lt;/td&gt;&lt;td&gt;61%&lt;/td&gt;&lt;td&gt;+690 字&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;分布严重右偏——几乎总是写多，很少写少。这不是随机误差，而是系统性偏差。&lt;/p&gt;
&lt;h3&gt;二、你和模型在数两种不同的“字”&lt;/h3&gt;
&lt;p&gt;系统统计字数用的是 &lt;code&gt;len(re.sub(r&apos;\s&apos;, &apos;&apos;, text))&lt;/code&gt;——去空白后数&lt;strong&gt;所有字符&lt;/strong&gt;。prompt 写的是“请写 2500 个&lt;strong&gt;字&lt;/strong&gt;”。&lt;/p&gt;
&lt;p&gt;模型理解的“字”是&lt;strong&gt;汉字&lt;/strong&gt;。系统数的“字符”包含标点、数字、字母。&lt;/p&gt;
&lt;p&gt;抽样 90 个输出的统计：&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;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CJK 汉字&lt;/td&gt;&lt;td&gt;84.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;标点符号&lt;/td&gt;&lt;td&gt;12.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数字+字母+其他&lt;/td&gt;&lt;td&gt;3.4%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;模型写了 2500 个汉字，系统计为 2950。模型写了 2600 个汉字，系统计为 3068，判超标。&lt;strong&gt;18% 的系统性偏差&lt;/strong&gt;——不是模型写多了，是量错了。&lt;/p&gt;
&lt;p&gt;改成只数 CJK 统一表意文字后，target=2900 的超标率从 31% 直接归零。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;count_cjk_chars&lt;/span&gt;&lt;span&gt;(text):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\u4e00&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\u9fff&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\u3400&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\u4dbf&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\U00020000&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\U0002a6df&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;一个函数的改动，比后面所有 prompt engineering 加起来都管用。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这种 bug 最阴险的地方在于：两边都“看起来对”。prompt 说“字”，开发者觉得“字符就是字”，各自合理，合在一起就是 18% 的幽灵偏差。在多模块系统里，同一概念的定义在不同组件间产生微妙漂移，是很经典的集成 bug。&lt;/p&gt;
&lt;h3&gt;三、砍 Token 预算反而更长&lt;/h3&gt;
&lt;p&gt;模型支持 thinking 模式，&lt;code&gt;max_completion_tokens&lt;/code&gt; 初始设 10000。直觉上砍到 5000 应该能压短输出。&lt;/p&gt;
&lt;p&gt;结果完全相反：&lt;/p&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;max_tokens&lt;/th&gt;&lt;th&gt;thinking&lt;/th&gt;&lt;th&gt;推理 tokens&lt;/th&gt;&lt;th&gt;输出 tokens&lt;/th&gt;&lt;th&gt;实际 CJK 汉字&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;10000&lt;/td&gt;&lt;td&gt;on&lt;/td&gt;&lt;td&gt;1204&lt;/td&gt;&lt;td&gt;2077&lt;/td&gt;&lt;td&gt;3041&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6000&lt;/td&gt;&lt;td&gt;on&lt;/td&gt;&lt;td&gt;689&lt;/td&gt;&lt;td&gt;2678&lt;/td&gt;&lt;td&gt;3840&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5000&lt;/td&gt;&lt;td&gt;on&lt;/td&gt;&lt;td&gt;502&lt;/td&gt;&lt;td&gt;2568&lt;/td&gt;&lt;td&gt;3765&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10000&lt;/td&gt;&lt;td&gt;off&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;2988&lt;/td&gt;&lt;td&gt;4313&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5000&lt;/td&gt;&lt;td&gt;off&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;2032&lt;/td&gt;&lt;td&gt;3005&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;max_tokens 从 10000 砍到 5000，thinking 开着，汉字数从 3041 &lt;strong&gt;涨&lt;/strong&gt;到 3765。&lt;/p&gt;
&lt;p&gt;原因：推理 token 和输出 token 共享预算池。预算紧缩时模型先砍推理（1204→502），而推理恰恰是模型规划全文结构、感知“该在哪收束”的能力。推理被压缩后，模型来不及想“该收了”就一路写下去。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;max_tokens: 10000  →  6000  →  5000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;推理 tokens:  1204  →   689  →   502  (↓)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;输出 tokens:  2077  →  2678  →  2568  (↑)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;实际汉字数:  3041  →  3840  →  3765  (↑)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;thinking=off 的那组，砍 max_tokens 确实有效（4313→3005），因为没有推理开销，物理上限直接生效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：在有 reasoning 能力的模型上，token 预算是非单调的控制变量。存在一个“推理充分”的阈值，低于它约束力反而变差。&lt;/p&gt;
&lt;h3&gt;四、两个样本的幻觉&lt;/h3&gt;
&lt;p&gt;修完计数口径后，试了三种 prompt 变体：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;baseline&lt;/strong&gt;：“目标 2500，范围 2000~3000”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;strict&lt;/strong&gt;：加“超出将被丢弃并触发重写，严重浪费算力”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;countdown&lt;/strong&gt;：把 2500 拆成四段预算，每段指定字数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先跑两个样本，三种变体全部 100% 合规。差点直接 commit。&lt;/p&gt;
&lt;p&gt;多跑两个样本：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;配置&lt;/th&gt;&lt;th&gt;Round 1 (n=2)&lt;/th&gt;&lt;th&gt;Round 2 (n=4)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;strict + 10k&lt;/td&gt;&lt;td&gt;100%&lt;/td&gt;&lt;td&gt;&lt;strong&gt;25%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;countdown + 10k&lt;/td&gt;&lt;td&gt;100%&lt;/td&gt;&lt;td&gt;&lt;strong&gt;25%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;baseline + 10k&lt;/td&gt;&lt;td&gt;100%&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;36 次调用汇总（target=2500 时的 CJK 输出分布）：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;最小值: 1671    最大值: 4247&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;平均值: 3087    标准差: 520&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;合规率(≤3000): 33%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;标准差 520——在这种方差下，2 个样本的“100%”纯粹是统计噪声。&lt;/p&gt;
&lt;p&gt;还有一个更隐蔽的坑：该模型在 thinking 模式下&lt;strong&gt;静默忽略 temperature 参数&lt;/strong&gt;，强制使用 1.0。API 不报错不警告。我以为在测 temp=0.6 和 temp=0.8 的差异，实际两组都跑在 1.0 上，所有关于 temperature 的“结论”全部作废。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;做 LLM 实验的铁律：先验证你改的参数确实生效了。API 的 silent failure 比报错更危险。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;五、0% 成功率的“LLM 压缩”&lt;/h3&gt;
&lt;p&gt;系统里还有一道“保险”：超标时调 LLM “压缩到 3000 字以内”。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;压缩尝试: 174 次&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;成功次数: 0 次&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;成功率:   0%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;一个不能精确计数的模型，让它“删减到精确字数”，不可能收敛。&lt;/p&gt;
&lt;h3&gt;六、根本原因排序&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&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;计数口径不一致（全字符 vs CJK 汉字）&lt;/td&gt;&lt;td&gt;~18%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;模型自然输出节奏不可控&lt;/td&gt;&lt;td&gt;~60%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LLM 压缩管道失效&lt;/td&gt;&lt;td&gt;~20%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;后处理逃逸路径（润色/审查重写后未重新校验）&lt;/td&gt;&lt;td&gt;~2%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;七、落地方案&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 对齐计数口径&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;代码数什么，prompt 就说什么。数 CJK 汉字就写“汉字数量”，别写“字数”。这一步 ROI 最高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 把 target 设在模型舒适区&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;该模型自然产出 3000~3500 CJK 汉字。target 设 2500 相当于要求它在自然产出的 70% 处刹车，它做不到。设 2900，合规范围覆盖自然区间，合规率到 95%。&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;target&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;2500&lt;/td&gt;&lt;td&gt;3000-3700&lt;/td&gt;&lt;td&gt;2000-3000&lt;/td&gt;&lt;td&gt;~25%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2900&lt;/td&gt;&lt;td&gt;3000-3500&lt;/td&gt;&lt;td&gt;2400-3400&lt;/td&gt;&lt;td&gt;~95%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3000&lt;/td&gt;&lt;td&gt;3000-3500&lt;/td&gt;&lt;td&gt;2500-3500&lt;/td&gt;&lt;td&gt;~85%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;3. prompt 加收束锚点&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;“写到 50% 进入高潮，65% 开始收束”。不能保证绝对有效，但可减少极端偏差。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 超标重试带反馈，不截断&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;截断会在任意位置切断逻辑，质量损失不可接受。重试时把具体数字反馈给模型——“上次 3500，上限 3000，请短一些”。比盲重试有效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 记录 token 明细&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推理 token、输出 token、finish_reason、CJK 字数。为模型升级后的回归检测提供 baseline。&lt;/p&gt;
&lt;h3&gt;八、回头看&lt;/h3&gt;
&lt;p&gt;这个问题的本质是&lt;strong&gt;用概率系统执行确定性约束&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;LLM 应用里有一个常见的隐含假设——“模型能精确遵循指令”。在分类、抽取这些输出空间小的任务上基本成立。但在长文本生成中，输出空间是指数级的，要求精确控制长度相当于在高维空间中画一条窄带，模型的生成过程没有这个约束机制。&lt;/p&gt;
&lt;p&gt;从控制论角度看，大多数 LLM 管线是&lt;strong&gt;开环系统&lt;/strong&gt;——生成一次就完事。加入重试 + 反馈是把开环变成闭环：生成 → 计数 → 判断 → 反馈 → 再生成。精度不够，用迭代补。&lt;/p&gt;
&lt;p&gt;关于实验方法：LLM 输出的标准差远大于传统软件。对标准差 500+ 的分布，需要几十个样本才能区分 10% 级别的差异。大部分人跑两三个 case 就下结论——不是不懂统计，是 API 太贵。但省下的 API 费用会以线上 bug 的形式加倍还回来。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;以上数据均基于 mimo-v2.5-pro。具体数值（超标率、推理 token 分配、自然产出区间等）不代表其他模型也是如此。核心结论——计数口径对齐、推理预算非单调性、迭代收敛优于截断——在方法论层面通用，但阈值需在目标模型上重新跑。&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>从 PRD 到 MR：企业级 Android 项目的 AI 编码工作流设计</title><link>https://folay.top/zh/blog/ai-coding-workflow</link><guid isPermaLink="true">https://folay.top/zh/blog/ai-coding-workflow</guid><description>在 Cursor 中搭建了一套 7 阶段的需求开发流程，AI 从读 PRD 到提交 MR，每一步都有门控和人工确认。</description><pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;本文介绍我在 Cursor 中构建的一套工作流：从 PRD 和设计稿输入开始，到技术方案生成、结构化任务拆解、逐任务编码、真机验证，最终自动提交 MR。整条链路被拆分为 7 个阶段，每个阶段设有门控和人工确认点，并通过分层加载、按需引入技能（Skills）和跨会话知识持久化，解决了 AI 在长链决策中质量衰减、每次需求“从零开始”两大核心痛点。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：本文内容基于一个具体的企业 Android 项目定制（MVP 架构、RxJava 规范、自有 UI 组件库），但底层设计思路——&lt;strong&gt;分阶段门控 + 知识持久化 + Token 分层 + 按需加载&lt;/strong&gt;——可推广至任何技术栈。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;整体架构&lt;/h2&gt;
&lt;p&gt;下图展示了从输入到交付的完整信息流：&lt;/p&gt;
&lt;div&gt;
flowchart LR
    PRD[&quot;PRD + 接口文档 + Figma&quot;] --&amp;gt; S0

    subgraph S0 [&quot;阶段0: 代码同步&quot;]
        Git[&quot;git fetch + checkout&lt;br /&gt;submodule update&quot;]
    end

    S0 --&amp;gt; S1

    subgraph S1 [&quot;阶段1: 需求分析&quot;]
        A1[&quot;读取跨需求知识&quot;] --&amp;gt; A2[&quot;获取 PRD/接口文档&lt;br /&gt;（钉钉MCP）&quot;]
        A2 --&amp;gt; A3[&quot;提取功能点 &amp;amp; 列出不清项&quot;]
        A3 --&amp;gt; A4[&quot;输出分析文档.md&quot;]
    end

    S1 --&amp;gt;|人工确认| S2

    subgraph S2 [&quot;阶段2: 设计稿分析&quot;]
        B1[&quot;读取 Figma / 截图&quot;] --&amp;gt; B2[&quot;px→dp/sp 转换&lt;br /&gt;布局结构推导&quot;]
        B2 --&amp;gt; B3[&quot;追加到分析文档&quot;]
    end

    S2 --&amp;gt;|人工确认| S3

    subgraph S3 [&quot;阶段3: 技术方案&quot;]
        C1[&quot;生成技术方案.md/.html&quot;] --&amp;gt; C2[&quot;结构化任务表&quot;]
        C2 --&amp;gt; C3[&quot;服务端联调.md&quot;]
    end

    S3 --&amp;gt;|方案确认| S4

    subgraph S4 [&quot;阶段4: 逐任务编码&quot;]
        direction LR
        D1[&quot;读取学习笔记&quot;] --&amp;gt; D2[&quot;搜索同类实现&quot;]
        D2 --&amp;gt; D3[&quot;加载对应 Skill&quot;]
        D3 --&amp;gt; D4[&quot;编码 + 模块编译&quot;]
        D4 --&amp;gt;|人工确认| D5[&quot;更新任务表&quot;]
        D5 --&amp;gt;|循环| D1
    end

    S4 --&amp;gt;|全部完成+自审| S5

    subgraph S5 [&quot;阶段5: 整包验证&quot;]
        E1[&quot;编译安装&quot;] --&amp;gt; E2[&quot;启动 App&quot;]
        E2 --&amp;gt; E3[&quot;Android MCP 真机验证&quot;]
        E3 --&amp;gt; E4[&quot;输出验证报告.html&quot;]
    end

    S5 --&amp;gt;|验证通过| S6

    subgraph S6 [&quot;阶段6: 提交 MR&quot;]
        F1[&quot;创建分支&quot;] --&amp;gt; F2[&quot;提交代码 &amp;amp; 子模块&quot;]
        F2 --&amp;gt; F3[&quot;创建 MR&quot;]
        F3 --&amp;gt; F4[&quot;沉淀项目经验&quot;]
    end

    S6 --&amp;gt; MR[&quot;MR 链接 + 验证报告&quot;]
&lt;/div&gt;
&lt;p&gt;整个流程由 Cursor Agent 驱动，底层依赖以下机制：&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;Rules&lt;/td&gt;&lt;td&gt;&lt;code&gt;.cursor/rules/*.mdc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;编码约束，按场景自动注入&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Skills&lt;/td&gt;&lt;td&gt;&lt;code&gt;.cursor/skills/*/SKILL.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;专项能力（API、DB、UI 生成等），按需加载&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Commands&lt;/td&gt;&lt;td&gt;&lt;code&gt;.cursor/commands/*.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;用户显式触发的快捷任务&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP Servers&lt;/td&gt;&lt;td&gt;&lt;code&gt;.cursor/mcp.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;外部工具桥接（文档、设计稿、真机、Git 平台）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;核心设计原理&lt;/h2&gt;
&lt;h3&gt;1. 门控机制：防止决策质量衰减&lt;/h3&gt;
&lt;p&gt;AI 在长链路中的决策质量随步骤数指数衰减。若让 AI 从 PRD 直通 MR 而不设中间校准，后期产物质量必然严重退化。这与人类开发同理——需求未理清就编码，必然返工。&lt;/p&gt;
&lt;p&gt;因此，我们在每个阶段结束时强制设置门控：AI 必须通过 &lt;code&gt;Ask Question&lt;/code&gt; 等待人工确认，方可进入下一阶段。关键门控点包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;阶段 3 → 4&lt;/strong&gt;：技术方案须经确认。方案若偏离需求，后续所有编码皆为无效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阶段 4 → 5&lt;/strong&gt;：全部任务完成 + 末次编译通过 + 编码规范自审通过。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阶段 5 → 6&lt;/strong&gt;：真机验证完成，PRD 功能点全部勾选。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;阶段 4 内部更细：每完成一个任务即确认一次。越早发现实现偏差，修复成本越低。&lt;/p&gt;
&lt;h3&gt;2. Token 分层控制：降低上下文开销&lt;/h3&gt;
&lt;p&gt;Cursor 每轮对话会注入 &lt;code&gt;alwaysApply: true&lt;/code&gt; 的 Rules。注入内容越多，可用的上下文窗口越小，留给代码分析的空间就越窄。我们将约束拆分为三层：&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;th&gt;Token 量级&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;L0 核心层&lt;/td&gt;&lt;td&gt;每轮对话&lt;/td&gt;&lt;td&gt;16 条禁止项 + 11 条必须项&lt;/td&gt;&lt;td&gt;~500&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;L1 编码层&lt;/td&gt;&lt;td&gt;编辑 &lt;code&gt;.java/.kt/.xml&lt;/code&gt; 时&lt;/td&gt;&lt;td&gt;生命周期、UI 组件规范、常见错误&lt;/td&gt;&lt;td&gt;~800&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;L2 领域层&lt;/td&gt;&lt;td&gt;编辑 API/DB 文件时&lt;/td&gt;&lt;td&gt;核心约束精简版 + 指向对应 Skill&lt;/td&gt;&lt;td&gt;各 ~200&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;核心层体积小（约 40 行）且几乎不变，对 Anthropic 的 prompt caching 非常友好——前缀越稳定，缓存命中率越高，API 调用成本越低。非编码阶段（如 git 操作、PRD 分析）仅加载 L0，Token 消耗控制在 500 以内。&lt;/p&gt;
&lt;h3&gt;3. Skills 按需加载：避免知识全量注入&lt;/h3&gt;
&lt;p&gt;Cursor Skills 通过 YAML frontmatter 中的 &lt;code&gt;description&lt;/code&gt; 让 AI 判断是否需要加载完整内容。AI 每轮对话开始时只读取所有 Skill 的 description（约 50 token/个），匹配当前任务时才加载完整正文。&lt;/p&gt;
&lt;p&gt;我们定义了 &lt;strong&gt;19 个专项 Skill&lt;/strong&gt;，覆盖以下能力域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心开发&lt;/strong&gt;：API 网络层、数据库操作、数据模型、埋点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UI 相关&lt;/strong&gt;：布局 XML 生成、设计稿转布局（含 Figma 智能学习）、MVP 页面生成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;质量保障&lt;/strong&gt;：模块编译验证、编码规范自审、脚本化代码审查&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;辅助工具&lt;/strong&gt;：Android SO/AAR 依赖分析、AB 实验下线、Wiki 生成/转换/同步检查&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程控制&lt;/strong&gt;：需求开发全流程（阶段 0→6）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全部 Skill description 合计约 1000 token，完整内容超过 25000 token——按需加载节省了 &lt;strong&gt;95% 以上的无关上下文&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如，“API 网络层开发” Skill 的 description 为“API 网络层开发规范”。当 AI 在阶段 4 需要编写网络请求时，自动匹配并加载 Skill 内的完整模板（API 类选择指南、请求模板、URL 构建方式、错误处理）。若任务与 API 无关，则该知识从不进入上下文。&lt;/p&gt;
&lt;h3&gt;4. 结构化任务表：突破上下文长度限制&lt;/h3&gt;
&lt;p&gt;Cursor 单会话上下文有上限。一个需求若包含 10+ 编码任务，执行到第 7、8 个时，AI 可能已遗忘前面完成了哪些任务。单纯依赖上下文记忆不可靠。&lt;/p&gt;
&lt;p&gt;我们在阶段 3 产出的技术方案中嵌入结构化任务表（Markdown 表格）：&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;th&gt;Skill&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;1&lt;/td&gt;&lt;td&gt;添加 xxx 字段&lt;/td&gt;&lt;td&gt;数据&lt;/td&gt;&lt;td&gt;data-model&lt;/td&gt;&lt;td&gt;parsers.xml&lt;/td&gt;&lt;td&gt;⬜ 待开始&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;新增 xxx 接口&lt;/td&gt;&lt;td&gt;逻辑&lt;/td&gt;&lt;td&gt;api-development&lt;/td&gt;&lt;td&gt;CoreXxx.java&lt;/td&gt;&lt;td&gt;⬜ 待开始&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;状态枚举：&lt;code&gt;⬜ 待开始&lt;/code&gt; → &lt;code&gt;🔄 进行中&lt;/code&gt; → &lt;code&gt;✅ 已完成&lt;/code&gt; → &lt;code&gt;⏭️ 已跳过&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;该表写入磁盘文件，AI 可随时重新读取最新状态，不依赖上下文窗口的“记忆力”。评估过 JSON 格式的追踪方案，最终选择 MD 表格——因为人类也能直接读写，AI 解析 MD 表格毫无困难，多维护一个 JSON 只会增加同步负担。&lt;/p&gt;
&lt;h3&gt;5. 自学习循环：经验跨需求沉淀&lt;/h3&gt;
&lt;p&gt;每次需求开发中，AI 需要搜索项目内的同类实现来理解编码习惯。第一次做某类需求时需搜索 3-5 个文件；第二次同类需求若仍从头搜索，即为效率损耗。&lt;/p&gt;
&lt;p&gt;解决方案是维护一个附属学习笔记文件 &lt;code&gt;convention-learnings.md&lt;/code&gt;。阶段 4 编码前，AI 读取该文件，跳过已记录的模式；编码完成后的自审阶段，将新发现的模式追加到笔记中。下一个需求的阶段 4 直接读取笔记，跳过重复搜索。&lt;/p&gt;
&lt;div&gt;
flowchart LR
    Start[&quot;编码前&quot;] --&amp;gt; Read[&quot;读 convention-learnings.md&quot;]
    Read --&amp;gt; Skip[&quot;跳过已记录模式&quot;]
    Skip --&amp;gt; Code[&quot;编码&quot;]
    Code --&amp;gt; SelfReview[&quot;自审&quot;]
    SelfReview --&amp;gt; Append[&quot;追加新发现&quot;]
    Append --&amp;gt; Next[&quot;下次需求读笔记&quot;]
&lt;/div&gt;
&lt;p&gt;关键约束：&lt;strong&gt;AI 从不修改 Skill 文件本身&lt;/strong&gt;，只维护附属笔记。原因是——若 AI 将错误模式写入 Skill（核心指令），后续所有需求将受污染；笔记文件出错，删除即可，影响可控。这一设计借鉴了 Hermes Agent 的 Closed Learning Loop，并增加了安全边界。&lt;/p&gt;
&lt;h3&gt;6. 跨需求知识持久化&lt;/h3&gt;
&lt;p&gt;另一个持久化文件 &lt;code&gt;project-memory.md&lt;/code&gt; 解决跨需求的“背景知识”传递问题：当前数据库版本、哪些接口已废弃、特殊的技术决策、踩过的坑。需求 A 结束后（阶段 6），AI 追加这些信息；需求 B 开始（阶段 1）自动加载，无需重新 grep 或翻阅历史提交。&lt;/p&gt;
&lt;p&gt;设定了淘汰机制：文件超过 80 行时清理过时条目，避免无限膨胀导致加载开销。&lt;/p&gt;
&lt;h2&gt;阶段详解&lt;/h2&gt;
&lt;h3&gt;阶段 0：代码同步&lt;/h3&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;submodule&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;foreach&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;git fetch origin &amp;amp;&amp;amp; git checkout origin/master&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;确保基于最新代码基线。完成后直接进入阶段 1，无需人工确认。&lt;/p&gt;
&lt;h3&gt;阶段 1：需求分析&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;加载 &lt;code&gt;project-memory.md&lt;/code&gt;，获得跨需求背景。&lt;/li&gt;
&lt;li&gt;读取 PRD + 接口文档。支持三种输入：&lt;strong&gt;钉钉文档链接&lt;/strong&gt;（通过钉钉 MCP 直接拉取 Markdown）、本地文件、用户粘贴文本。&lt;/li&gt;
&lt;li&gt;提取客户端功能点，列出不清项和边界遗漏；核对接口文档与 PRD 是否矛盾。&lt;/li&gt;
&lt;li&gt;输出 &lt;code&gt;docs/[需求名]/分析.md&lt;/code&gt; 并请求确认。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;阶段 2：设计稿分析&lt;/h3&gt;
&lt;p&gt;有 Figma 链接时通过 Framelink MCP 读取图层/样式/布局，否则使用 PRD 截图。调用设计稿转换 Skill 执行 px→dp/sp 转换、颜色格式映射、布局结构推导。产出追加至分析文档。&lt;/p&gt;
&lt;h3&gt;阶段 3：技术方案&lt;/h3&gt;
&lt;p&gt;产出三个文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;技术方案.md&lt;/code&gt;（含任务表）及其 &lt;code&gt;.html&lt;/code&gt; 可视化版本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;服务端联调.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;技术方案中的任务表是后续编码的唯一依据。每个任务标注了类型（数据/逻辑/UI）、应调用的 Skill、涉及文件列表。该表同时作为进度追踪看板。&lt;/p&gt;
&lt;h3&gt;阶段 4：逐任务编码&lt;/h3&gt;
&lt;p&gt;每个任务执行固定流水线：&lt;/p&gt;
&lt;div&gt;
flowchart LR
    A[&quot;读 convention-learnings.md&quot;] --&amp;gt; B[&quot;搜索同类实现&lt;br /&gt;（仅新场景）&quot;]
    B --&amp;gt; C[&quot;加载对应 Skill&quot;]
    C --&amp;gt; D[&quot;编码&quot;]
    D --&amp;gt; E[&quot;模块编译&quot;]
    E --&amp;gt; F{&quot;Ask Question 确认&quot;}
    F --&amp;gt;|通过| G[&quot;更新任务表状态&quot;]
    F --&amp;gt;|驳回| D
    G --&amp;gt; H[&quot;下一任务&quot;]
&lt;/div&gt;
&lt;p&gt;全部任务完成后，执行&lt;strong&gt;编码规范自审&lt;/strong&gt;（5 项 Checklist）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;禁止项检查（如 &lt;code&gt;context.getColor()&lt;/code&gt;、&lt;code&gt;SharedPreferences&lt;/code&gt; 直接调用）&lt;/li&gt;
&lt;li&gt;常量一致性&lt;/li&gt;
&lt;li&gt;仓库特有约束&lt;/li&gt;
&lt;li&gt;Import 完整性（禁止正文中全限定类名）&lt;/li&gt;
&lt;li&gt;Android 安全自审（&lt;code&gt;android:exported&lt;/code&gt; 声明、硬编码 Token、新权限、Debug 代码隔离）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自审通过后，将新发现的编码模式追加到 &lt;code&gt;convention-learnings.md&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;阶段 5：整包验证&lt;/h3&gt;
&lt;p&gt;这里执行的是真机 UI 验证，而非单元测试。流程如下：&lt;/p&gt;
&lt;div&gt;
flowchart LR
    A[&quot;PRD 功能点清单&quot;] --&amp;gt; B[&quot;编译安装&quot;]
    B --&amp;gt; C[&quot;启动 App&quot;]
    C --&amp;gt; D{&quot;Android MCP&lt;br /&gt;逐屏验证&quot;}
    D --&amp;gt; E[&quot;功能点全部勾选?&quot;]
    E --&amp;gt;|否| F[&quot;定位问题&quot;]
    F --&amp;gt; C
    E --&amp;gt;|是| G[&quot;产出验证报告.html&quot;]
&lt;/div&gt;
&lt;p&gt;Android MCP 可执行 &lt;code&gt;dump_ui_hierarchy&lt;/code&gt;、点击操作、截屏。AI 获取 UI 树后逐个功能点对照 PRD 检查。验证报告为单文件 HTML（内联样式，不入 git），包含功能点卡片、状态过滤、环境信息。&lt;/p&gt;
&lt;h3&gt;阶段 6：提交 MR&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;变更范围确认（主工程 + Git 子模块）。&lt;/li&gt;
&lt;li&gt;创建分支，更新 &lt;code&gt;.gitmodules&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;提交并推送。&lt;/li&gt;
&lt;li&gt;创建子模块 MR → 主工程 MR。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;沉淀项目经验&lt;/strong&gt;：将本次需求的接口变化、数据库版本变更、踩坑记录等追加到 &lt;code&gt;project-memory.md&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;MCP 集成与工具链&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;MCP Server&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;钉钉文档 MCP&lt;/td&gt;&lt;td&gt;读取钉钉在线文档（PRD/接口文档），返回 Markdown&lt;/td&gt;&lt;td&gt;阶段 1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Framelink MCP for Figma&lt;/td&gt;&lt;td&gt;读取 Figma 设计稿图层/样式/布局&lt;/td&gt;&lt;td&gt;阶段 2、5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Android MCP&lt;/td&gt;&lt;td&gt;真机 UI dump、点击、截屏&lt;/td&gt;&lt;td&gt;阶段 5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Code Review MCP&lt;/td&gt;&lt;td&gt;远程 MR diff 读取 + 评论提交&lt;/td&gt;&lt;td&gt;可选（阶段 5.5）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Context7&lt;/td&gt;&lt;td&gt;查询第三方库文档（Fresco、RxJava 等）&lt;/td&gt;&lt;td&gt;按需&lt;/td&gt;&lt;/tr&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;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;设计思考与感悟&lt;/h2&gt;
&lt;h3&gt;1. 为什么不用并行 Agent？&lt;/h3&gt;
&lt;p&gt;需求开发本质上是串行链路：技术方案决定编码方向，编码决定验证内容，验证决定 MR 范围。并行 Agent 在此场景下没有独立的工作单元可以分配，反而会导致代码冲突和上下文割裂。Cursor 的 &lt;code&gt;/multitask&lt;/code&gt; 适合真正独立的并行任务（如同时运行 lint 和 test），不适合有依赖关系的流水线。&lt;/p&gt;
&lt;h3&gt;2. 为什么每个阶段都要门控？&lt;/h3&gt;
&lt;p&gt;两个原因：一是质量校准——AI 在长链中的决策质量随步骤数衰减，每次确认是一次“重新对齐”；二是责任边界——用户确认后继续，若出现问题可精确定位到哪个阶段的决策有误。&lt;/p&gt;
&lt;h3&gt;3. 为什么 Skills 学习只写附属文件？&lt;/h3&gt;
&lt;p&gt;直接让 AI 修改 Skill 文件存在两个风险：第一，prompt injection——若 AI 将错误的模式写入 Skill，后续所有需求都会受影响；第二，知识退化——累积的“学习成果”可能覆盖人工精心设计的规范。附属文件 &lt;code&gt;convention-learnings.md&lt;/code&gt; 允许人随时审阅、删除错误条目，且清空即可“重置学习”，风险远低于修改 Skill 本身。&lt;/p&gt;
&lt;h3&gt;4. Token 经济学带来的设计约束&lt;/h3&gt;
&lt;p&gt;Claude Code 社区的实测数据表明：&lt;code&gt;alwaysApply&lt;/code&gt; 内容从 800 token 减到 500 token，prompt cache hit 率可从 12% 跃升至 61%。这是因为 Anthropic 的 prompt caching 对前缀匹配敏感——系统 prompt 越稳定，缓存命中越高，成本越低。这解释了我们将核心约束层拆得极薄且保持不变的决策。&lt;/p&gt;
&lt;h3&gt;5. 终极思考：流程自动化 vs 通用智能&lt;/h3&gt;
&lt;p&gt;当前工作流并非“AI 替代人类”，而是&lt;strong&gt;人类定义流程、AI 执行任务&lt;/strong&gt;的混合模式。门控点由人决策，AI 负责重复性高、规则明确的执行环节（读文档、写代码、跑验证）。这种分工在现阶段最为务实——AI 的生成能力和推理能力仍有边界，但流程化编排可以最大化其确定性收益。&lt;/p&gt;
&lt;p&gt;如果你的团队也在尝试 AI 辅助开发，建议从最薄弱的环节切入：&lt;strong&gt;在关键决策点让 AI 停下来等你确认&lt;/strong&gt;，而不是让它一口气跑到底。这一改动往往能带来最直接的产出质量提升。在此基础上，再逐步引入知识持久化、分层加载等机制，构建适合自身项目的全流程自动化体系。&lt;/p&gt;</content:encoded></item><item><title>dead-code-pruner：布尔常量折叠后的死代码自动清理</title><link>https://folay.top/zh/blog/dead-code-clean</link><guid isPermaLink="true">https://folay.top/zh/blog/dead-code-clean</guid><description>一个零依赖的 Python 工具，给它一份常量映射表，它会迭代执行常量替换、布尔简化、if 块消除、方法内联、死方法清理，直到没有更多变更。</description><pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大型项目总会积累一些“永远为 true”或“永远为 false”的布尔标志——&lt;code&gt;BuildConfig.IS_PRODUCTION&lt;/code&gt;、&lt;code&gt;FeatureFlags.isLegacyMode()&lt;/code&gt;、各种运行时开关。标志的值固定下来之后，被它守护的代码就变成了死代码。&lt;/p&gt;
&lt;p&gt;几行 &lt;code&gt;if(false)&lt;/code&gt; 不影响编译，但几百处散布在十几个模块里就不一样了：IDE 索引变慢、代码搜索噪音增大、新人被废弃逻辑误导、APK 体积白白膨胀。&lt;/p&gt;
&lt;p&gt;手动改几百处不现实，IDE 的 Inspections 只能处理最简单的 &lt;code&gt;if(true)&lt;/code&gt; 场景。写了一个 Python 工具来做这件事——&lt;a href=&quot;https://github.com/OldJii/dead-code-pruner&quot;&gt;dead-code-pruner&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;它做什么&lt;/h2&gt;
&lt;p&gt;给它一份配置文件，告诉它哪些表达式恒为 &lt;code&gt;true&lt;/code&gt; 或 &lt;code&gt;false&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;replacements&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;pattern&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;BuildConfig.IS_PRODUCTION&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;pattern&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;FeatureFlags.isLegacyMode&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;工具会执行一个 6 步流水线。每一步的产出可能给下一步创造新的简化机会，所以整条流水线循环执行直到收敛——某一轮没有任何文件变更时停止。&lt;/p&gt;
&lt;div&gt;
flowchart TB
    subgraph Phase1 [&quot;Phase 1: 常量折叠 + 布尔简化&quot;]
        S1[step1 常量替换] --&amp;gt; S2[step2 简单布尔]
        S2 --&amp;gt; S3[step3 复合布尔]
        S3 --&amp;gt; S4[step4 if 块消除]
        S4 -- 有变更 --&amp;gt; S1
    end

    subgraph Phase2 [&quot;Phase 2: 方法内联 + 级联简化&quot;]
        T1[step5 恒返回方法内联] --&amp;gt; T2[step2 简单布尔]
        T2 --&amp;gt; T3[step3 复合布尔]
        T3 --&amp;gt; T4[step4 if 块消除]
        T4 -- 有变更 --&amp;gt; T1
    end

    subgraph Phase3 [&quot;Phase 3: 死方法清理 + 级联简化&quot;]
        U1[step6 死方法清理] --&amp;gt; U2[step2 简单布尔]
        U2 --&amp;gt; U3[step3 复合布尔]
        U3 --&amp;gt; U4[step4 if 块消除]
        U4 -- 有变更 --&amp;gt; U1
    end

    Phase1 -- 收敛 --&amp;gt; Phase2
    Phase2 -- 收敛 --&amp;gt; Phase3
    Phase3 -- 收敛 --&amp;gt; E((完成))
&lt;/div&gt;
&lt;h2&gt;为什么需要迭代收敛&lt;/h2&gt;
&lt;p&gt;考虑这段代码：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (BuildConfig.IS_PRODUCTION) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;showProduction&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;isLegacy&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;handleLegacy&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Phase 1 的 step1 把 &lt;code&gt;BuildConfig.IS_PRODUCTION&lt;/code&gt; 替换为 &lt;code&gt;true&lt;/code&gt;，step4 展开 &lt;code&gt;if(true)&lt;/code&gt; 块、删除 &lt;code&gt;else&lt;/code&gt; 块。一轮结束。&lt;/p&gt;
&lt;p&gt;但 &lt;code&gt;handleLegacy()&lt;/code&gt; 被删除后，&lt;code&gt;isLegacy()&lt;/code&gt; 方法可能变成无调用者。Phase 3 的 step6 扫描发现它是死方法，删除定义。而 &lt;code&gt;isLegacy()&lt;/code&gt; 内部可能还调用了其他方法……级联效应。&lt;/p&gt;
&lt;p&gt;只跑一遍流水线，这些级联产生的死代码会被遗漏。&lt;/p&gt;
&lt;h2&gt;6 步详解&lt;/h2&gt;
&lt;h3&gt;step1：常量替换&lt;/h3&gt;
&lt;p&gt;读取配置文件，把匹配的表达式替换为布尔字面量。所有替换操作共享一个 tokenizer，把源码拆成“代码段”和“非代码段”（注释、字符串字面量），只对代码段做替换。不做这层保护的话，&lt;code&gt;// BuildConfig.IS_PRODUCTION&lt;/code&gt; 这种注释也会被改掉。&lt;/p&gt;
&lt;h3&gt;step2：简单布尔简化&lt;/h3&gt;
&lt;p&gt;处理一元和二元布尔运算。工具内部注册了完整的模式表：&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;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;!true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;!false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true == true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;false == false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true == false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;false == true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true != false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;false != true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true != true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;false != false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;另外还处理冗余括号：&lt;code&gt;(true)&lt;/code&gt; → &lt;code&gt;true&lt;/code&gt;、&lt;code&gt;(false)&lt;/code&gt; → &lt;code&gt;false&lt;/code&gt;，但只在安全的上下文中——不能剥离 &lt;code&gt;if(true)&lt;/code&gt; 中的括号（语法要求），也不能剥离函数调用 &lt;code&gt;foo(true)&lt;/code&gt; 中的括号。工具会检查括号前一个 token 来判断安全性：&lt;code&gt;if&lt;/code&gt;/&lt;code&gt;while&lt;/code&gt;/&lt;code&gt;for&lt;/code&gt; → 不剥离，&lt;code&gt;return&lt;/code&gt;/&lt;code&gt;throw&lt;/code&gt;/运算符/行首 → 安全剥离。&lt;/p&gt;
&lt;h3&gt;step3：复合布尔简化&lt;/h3&gt;
&lt;p&gt;处理短路运算和三元表达式，正确处理运算符优先级。覆盖的模式：&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;code&gt;false &amp;amp;&amp;amp; EXPR&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;短路，EXPR 被整体删除&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EXPR &amp;amp;&amp;amp; false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;反向短路&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true &amp;amp;&amp;amp; EXPR&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;EXPR&lt;/code&gt;&lt;/td&gt;&lt;td&gt;恒等消除&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EXPR &amp;amp;&amp;amp; true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;EXPR&lt;/code&gt;&lt;/td&gt;&lt;td&gt;同上，连同前导注释行一起移除&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true || EXPR&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;短路&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EXPR || true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;反向短路&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;false || EXPR&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;EXPR&lt;/code&gt;&lt;/td&gt;&lt;td&gt;恒等消除&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EXPR || false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;EXPR&lt;/code&gt;&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true ? X : Y&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;X&lt;/code&gt;&lt;/td&gt;&lt;td&gt;三元消除&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;false ? X : Y&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;Y&lt;/code&gt;&lt;/td&gt;&lt;td&gt;三元消除&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;false + &quot;&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;false&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;字符串拼接折叠&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true + &quot;&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;true&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这里有个容易出错的优先级问题：&lt;code&gt;true || A &amp;amp;&amp;amp; B&lt;/code&gt; 应该简化为 &lt;code&gt;true&lt;/code&gt;，而不是 &lt;code&gt;true || A&lt;/code&gt; 然后留下 &lt;code&gt;B&lt;/code&gt;。因为 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 优先级高于 &lt;code&gt;||&lt;/code&gt;，&lt;code&gt;A &amp;amp;&amp;amp; B&lt;/code&gt; 是 &lt;code&gt;||&lt;/code&gt; 的一个操作数，整体应该被消除。工具在处理 &lt;code&gt;||&lt;/code&gt; 时会把 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 表达式当作一个整体跳过。&lt;/p&gt;
&lt;p&gt;嵌套三元表达式 &lt;code&gt;condition ? (innerCond ? A : B) : C&lt;/code&gt; 需要追踪括号深度和三元嵌套深度来找到正确的 &lt;code&gt;:&lt;/code&gt; 位置，不能简单地找第一个冒号。&lt;/p&gt;
&lt;p&gt;每个模式匹配还要避开 &lt;code&gt;==&lt;/code&gt; / &lt;code&gt;!=&lt;/code&gt; 上下文——&lt;code&gt;x == true&lt;/code&gt; 中的 &lt;code&gt;true&lt;/code&gt; 是比较操作数，不能当作短路的左侧。&lt;/p&gt;
&lt;h3&gt;step4：if 块消除&lt;/h3&gt;
&lt;p&gt;覆盖的场景：&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;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;if (true) { A }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;A&lt;/code&gt;（展开块内容）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;if (true) { A } else { B }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;A&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;if (true) { A } else if (X) { B } else { C }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;A&lt;/code&gt;（删除整个 else-if 链）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;if (false) { A }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;删除&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;if (false) { A } else { B }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;B&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;if (false) { A } else if (X) { B } else { C }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;if (X) { B } else { C }&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;if (false) return X;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;删除（单行无花括号）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;删除死代码时不能跨 switch/case 标签。Java 的 case 之间没有显式闭合，删多了会把相邻 case 的代码吃掉。&lt;/p&gt;
&lt;h3&gt;step5：恒返回方法内联&lt;/h3&gt;
&lt;p&gt;扫描所有方法定义，找到只有 &lt;code&gt;return true;&lt;/code&gt; 或 &lt;code&gt;return false;&lt;/code&gt; 的方法，在调用处替换为常量值：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 内联前&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isLocal&lt;/span&gt;&lt;span&gt;() { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;isLocal&lt;/span&gt;&lt;span&gt;()) { ... }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 内联后&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;) { ... }  &lt;/span&gt;&lt;span&gt;// 下一轮被 step4 清理&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;有个微妙的 bug：&lt;code&gt;shouldBlock();&lt;/code&gt; 作为独立语句时，内联后变成 &lt;code&gt;false;&lt;/code&gt;——不是合法的 Java 语句。解决方案是内联后扫描文件，移除所有独立的 &lt;code&gt;true;&lt;/code&gt;/&lt;code&gt;false;&lt;/code&gt; 行。&lt;/p&gt;
&lt;h3&gt;step6：死方法清理&lt;/h3&gt;
&lt;p&gt;找到空 void 方法和恒返回布尔方法，移除定义和调用处。&lt;/p&gt;
&lt;p&gt;这一步需要构建内存引用索引。项目可能有上万个源文件，对每个方法做全局 grep 查引用耗时不可接受。做法是一次遍历所有文件建立方法名→引用位置的倒排索引，后续查询走内存，复杂度从 O(N×M) 降到 O(N+M)。实测从“跑不完”变为 40 秒完成。&lt;/p&gt;
&lt;p&gt;安全边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;参数数量精确匹配&lt;/strong&gt;——&lt;code&gt;render()&lt;/code&gt; 和 &lt;code&gt;render(dialog)&lt;/code&gt; 是不同的方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非 private 方法只内联调用，不删定义&lt;/strong&gt;——protected/package-private 方法可能被子类 &lt;code&gt;@Override&lt;/code&gt;，静态分析无法确定安全性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跳过接口方法、抽象方法、&lt;code&gt;@Override&lt;/code&gt; 方法&lt;/strong&gt;——构建类继承层次，排除框架约束的方法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链式调用保护&lt;/strong&gt;——&lt;code&gt;method().subscribe()&lt;/code&gt; 这种链不会因为删除 void 调用而断裂&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://github.com/OldJii/dead-code-pruner.git&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 在项目根目录放一份配置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dead-code-pruner/pruner.example.yaml&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pruner.yaml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 编辑 pruner.yaml&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 预览&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;python3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dead-code-pruner/prune.py&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dry-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 执行&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;python3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dead-code-pruner/prune.py&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 编译验证&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./gradlew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;compileDebugJavaWithJavac&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;可以只跑某一阶段（&lt;code&gt;--phase 1/2/3&lt;/code&gt;），也可以单独执行某个 step（&lt;code&gt;python3 step3_compound_boolean.py .&lt;/code&gt;）。零外部依赖，Python 3.8+。YAML 配置需要 PyYAML，不想装的话用 JSON 格式。&lt;/p&gt;
&lt;h2&gt;实战&lt;/h2&gt;
&lt;p&gt;用这个工具对一个中大型 Android 项目做了一次完整清理。项目中有几百处运行时布尔判断。配置文件写一行 &lt;code&gt;pattern: &quot;BuildConfig.IS_PRODUCTION&quot;, value: true&lt;/code&gt;，跑完三阶段流水线，布尔简化 → 方法内联 → 死方法清理全部自动完成。&lt;/p&gt;
&lt;p&gt;之后配合 Android 的 &lt;code&gt;shrinkResources&lt;/code&gt; 和 AS 的 Remove Unused Resources 做资源清理。需要注意 &lt;code&gt;getIdentifier()&lt;/code&gt; 动态资源引用——&lt;code&gt;shrinkResources&lt;/code&gt; 检测不到这种引用关系，匹配模式要加入 &lt;code&gt;keep.xml&lt;/code&gt; 白名单。&lt;/p&gt;
&lt;p&gt;最终 APK 从 125 MB 降到 113 MB，减少约 12 MB（接近 10%）。DEX 减了 6 MB（源码级清理 + R8 优化的正向循环），res 减了 5 MB。&lt;/p&gt;
&lt;h2&gt;持续维护&lt;/h2&gt;
&lt;p&gt;清理不是一次性的。每次 merge 主分支都可能引入新的条件判断代码。工具设计成可重复执行——merge 后跑一次 &lt;code&gt;python3 prune.py .&lt;/code&gt; 就能自动清理增量，不需要人再理解业务逻辑。&lt;/p&gt;
&lt;h2&gt;局限&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;针对 Java/Kotlin 源文件，基于模式匹配而非语义分析&lt;/li&gt;
&lt;li&gt;无法处理中间变量赋值（&lt;code&gt;boolean x = BuildConfig.FOO; if (x) ...&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;step6 死方法检测偏保守：只移除真正空体或单常量返回的方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;保守是有意为之——空壳方法留着不会出 bug，删错了会。&lt;/p&gt;
&lt;h2&gt;设计选择&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;为什么不用 AI / IDE？&lt;/strong&gt; 评估过 Cursor 逐文件改和 AS Inspections。AI 单次 context window 覆盖不了上万文件的完整引用链，每次结果不确定。IDE 只能处理最简单的 &lt;code&gt;if(true)&lt;/code&gt;，嵌套三元、短路运算、级联死代码一概不管。脚本行为是确定性的——同一输入同一输出，跑错了修脚本重跑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么分 6 个独立文件？&lt;/strong&gt; 每个 step 可以独立运行和调试。排查问题时可以只跑 step3 看复合布尔简化的结果，不用跑整条流水线。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么用 subprocess 而不是 import？&lt;/strong&gt; 隔离性。每个 step 有自己的文件扫描逻辑和状态，进程隔离避免共享状态的 bug。实测整条流水线 40 秒级别，进程启动开销可忽略。&lt;/p&gt;</content:encoded></item><item><title>MCP Dock：给 14 个 AI 客户端统一管配置</title><link>https://folay.top/zh/blog/mcp-dock</link><guid isPermaLink="true">https://folay.top/zh/blog/mcp-dock</guid><description>做了个桌面端工具，统一管理 Cursor、VS Code、Claude Code 等客户端的 MCP 配置。</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;之前写过两篇 MCP 相关的——用 Cursor 做 Code Review、Cursor × Figma 技术调研。写完之后最大的感受不是 MCP 有多强，而是配置有多烦。&lt;/p&gt;
&lt;p&gt;每加一个 MCP Server，都得手动编辑 &lt;code&gt;claude_desktop_config.json&lt;/code&gt;、&lt;code&gt;.cursor/mcp.json&lt;/code&gt;、&lt;code&gt;.vscode/mcp.json&lt;/code&gt;……格式还不一样。手里 AI 工具越来越多，每次都要改好几个文件，烦了。&lt;/p&gt;
&lt;p&gt;所以做了 MCP Dock，一个桌面端，把配置管理统一起来。&lt;/p&gt;
&lt;p&gt;项目地址：&lt;a href=&quot;https://github.com/OldJii/mcp-dock&quot;&gt;OldJii/mcp-dock&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;演示&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;功能&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;商店&lt;/strong&gt;：聚合了 9200+ MCP Server，可以浏览、搜索、按 Stars 排序。另外有 3100+ AI Skills。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一键安装&lt;/strong&gt;：选个 Server，勾选要装到哪些客户端，填参数，点安装。配置会自动写入对应客户端的文件。目前支持 Cursor、VS Code、Claude Code、Gemini CLI、Codex CLI、Windsurf、Zed、TRAE、TRAE CN、Kiro、Opencode、JetBrains、Antigravity、OpenClaw，一共 14 个。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inspector&lt;/strong&gt;：不想为了测一个 Server 而启动 IDE。内置了 Inspector，连上 Server 就能看 Tools 和 Resources 的返回。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;历史记录&lt;/strong&gt;：每次改配置前自动备份，改坏了看 diff，一键回滚。&lt;/p&gt;
&lt;h2&gt;配置差异&lt;/h2&gt;
&lt;p&gt;做了之后才发现，虽然大家都支持 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;th&gt;Server 字段&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Cursor&lt;/td&gt;&lt;td&gt;JSON&lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.cursor/mcp.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;mcpServers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;VS Code&lt;/td&gt;&lt;td&gt;JSONC&lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.vscode/mcp.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;servers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Claude Code&lt;/td&gt;&lt;td&gt;JSON&lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.claude.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;mcpServers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;OpenClaw&lt;/td&gt;&lt;td&gt;JSON5&lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.openclaw/openclaw.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;mcp.servers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Windsurf&lt;/td&gt;&lt;td&gt;JSON&lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.codeium/windsurf/mcp_config.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;mcpServers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JetBrains&lt;/td&gt;&lt;td&gt;JSON&lt;/td&gt;&lt;td&gt;各 IDE 配置目录&lt;/td&gt;&lt;td&gt;&lt;code&gt;mcpServers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;JSON、JSON5、JSONC 三种格式混着来，字段路径也不统一。内部做了一层抽象来处理。&lt;/p&gt;
&lt;p&gt;JetBrains 更麻烦——它不是一个工具而是一系列 IDE（IntelliJ IDEA、WebStorm、PyCharm、GoLand……），每个 IDE 配置目录不一样，还分 CE 和 Ultimate 版本。MCP Dock 会扫描系统里所有 JetBrains 实例，统一管理。&lt;/p&gt;
&lt;h2&gt;本地优先&lt;/h2&gt;
&lt;p&gt;配置文件、API Key、历史记录全在本地，不过云端。MCP 配置里经常有 API Key 和 Token，这些东西不应该离开用户的机器。&lt;/p&gt;
&lt;p&gt;商店数据走 CDN，国内也能快速加载。没有账号体系，不需要登录。&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# macOS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OldJii/tap/mcp-dock&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 或手动下载&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# https://github.com/OldJii/mcp-dock/releases&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;支持 macOS（Intel / Apple Silicon）、Windows、Linux（AppImage / deb）。&lt;/p&gt;</content:encoded></item><item><title>Android 16KB Page Size 适配</title><link>https://folay.top/zh/blog/16k</link><guid isPermaLink="true">https://folay.top/zh/blog/16k</guid><description>Android 16KB 页面大小适配实战记录，包括 SO 依赖梳理、真机验证、SoLoader 移除和 Fresco 升级等关键环节。</description><pubDate>Wed, 18 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Google Play 要求 2025 年 11 月 1 日起，所有新应用及更新必须支持 16KB 页面设备。借着这次适配，记录下过程中踩过的坑和一些经验。&lt;/p&gt;

&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;Android 15 引入了 16KB 页面大小模式。传统 Linux 和 Android 使用 4KB 页面，而 ARM 架构天然支持 4KB / 16KB / 64KB 三种页面大小。Google 推动 16KB 的核心收益是&lt;strong&gt;更大的 TLB 覆盖范围和更少的页表层级&lt;/strong&gt;，在内存密集型场景下可以显著减少 TLB miss，提升整体性能。Pixel 8/8a 等设备已在 Android 16 上默认启用 16KB 模式。&lt;/p&gt;
&lt;p&gt;表面上看，适配就是给 SO 加个 &lt;code&gt;-Wl,-z,max-page-size=16384&lt;/code&gt; 链接参数，但实际做下来远没这么简单。我们项目 APK 中有 133 个 arm64 SO，来源极为分散，最终涉及 9 个基础库仓库和 6 个业务子模块的联动修改。&lt;/p&gt;
&lt;h2&gt;SO 来源梳理&lt;/h2&gt;
&lt;p&gt;一个中大型 App 的 SO 来源通常可以分为这几类：自研 NDK 编译产出、第三方 SDK 的 AAR 内嵌、传递依赖引入（你甚至不知道它的存在）、远程动态下发（不在 APK 中，但运行时加载）。&lt;/p&gt;
&lt;p&gt;第一步是建立完整清单，把 APK 中所有 SO 按依赖分组，逐一追溯到具体的 Maven 坐标和版本号。这是后续一切工作的基础。我们用 Gradle 的依赖树配合 &lt;code&gt;unzip -l&lt;/code&gt; APK 的方式整理出了全量清单，每个 SO 对应到具体的依赖传递链——这个清单在后续排查问题时非常有用。&lt;/p&gt;
&lt;h2&gt;p_align 声明不可信&lt;/h2&gt;
&lt;p&gt;Android 16 的 &lt;code&gt;linker64&lt;/code&gt; 通过 &lt;code&gt;FixMinAlignFor16KiB()&lt;/code&gt; 从 LOAD 段的&lt;strong&gt;实际文件偏移&lt;/strong&gt;计算真实兼容页面大小，不依赖 ELF header 中的 &lt;code&gt;p_align&lt;/code&gt; 声明值。&lt;/p&gt;
&lt;p&gt;实际适配中发现，部分 SDK 只在 ELF header 中声明了 &lt;code&gt;p_align = 0x4000&lt;/code&gt;，但 LOAD 段的文件偏移仍按 4KB 对齐，这种情况在 linker64 上会直接加载失败。和 SDK 方沟通时需要明确告知：&lt;strong&gt;仅改 &lt;code&gt;p_align&lt;/code&gt; 声明不够，必须重新编译链接&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;唯一可靠的验证方式是真机加载：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;adb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;libxxx.so&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/data/local/tmp/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;adb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/system/bin/linker64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/data/local/tmp/libxxx.so&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;成功会输出 &lt;code&gt;load_bias=0x...&lt;/code&gt;，失败会报 &lt;code&gt;alignment (4096) is not a multiple of the page size (16384)&lt;/code&gt;。&lt;code&gt;readelf -l&lt;/code&gt; 可以辅助验证，但以 linker64 真机结果为准。&lt;/p&gt;
&lt;h2&gt;分类处理&lt;/h2&gt;
&lt;p&gt;不兼容的 SO 没有统一的修复方式，需要根据情况分类处理。&lt;/p&gt;
&lt;h3&gt;有源码的自研 SO&lt;/h3&gt;
&lt;p&gt;最简单的情况，NDK 编译时添加链接参数即可：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Android.mk&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# CMakeLists.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;target_link_options(your_lib PRIVATE -Wl,-z,max-page-size=16384)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;注意：如果 SO 静态链接了第三方库（如 OpenSSL），所有被链接的库也必须用 16KB 对齐编译，遗漏任何一个静态库都会导致最终 SO 不兼容。我们就踩过这个坑——重编译 FFmpeg 时遗漏了 OpenSSL 的编译脚本，导致 HTTPS 视频无法播放，排查了好一会儿才定位到是 OpenSSL 的 SO 没有一起重编译。&lt;/p&gt;
&lt;p&gt;另外一个比较隐蔽的问题是 &lt;strong&gt;FFmpeg 旧版本的汇编兼容性&lt;/strong&gt;。FFmpeg 3.4 的 aarch64 NEON 汇编（&lt;code&gt;fft_neon.S&lt;/code&gt;、&lt;code&gt;h264idct_neon.S&lt;/code&gt;、&lt;code&gt;sbrdsp_neon.S&lt;/code&gt; 等）存在 PIC 重定位不兼容的问题，在 16KB 对齐模式下编译会失败。短期方案是 &lt;code&gt;--disable-asm&lt;/code&gt; 关闭汇编优化（硬件解码不受影响），长期需要升级到 FFmpeg 4.x+ 才能恢复汇编加速。&lt;/p&gt;
&lt;h3&gt;第三方 SDK&lt;/h3&gt;
&lt;p&gt;联系 SDK 方获取 16KB 兼容版本。大部分主流 SDK 在 2025 年已经或正在发布兼容版本。&lt;/p&gt;
&lt;h3&gt;远程下发的 SO&lt;/h3&gt;
&lt;p&gt;如果 SO 不打包在 APK 中，而是通过远程下发机制在运行时加载，可以在 &lt;code&gt;build.gradle&lt;/code&gt; 中排除：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;android {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;packagingOptions {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;jniLibs {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;excludes &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;**/libxxx.so&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这些 SO 由远程下发机制保证版本兼容性，不受 APK 打包限制。但要注意 &lt;strong&gt;&lt;code&gt;excludes&lt;/code&gt; 配置和远程下发清单的一致性&lt;/strong&gt;，我们就发现过某个 SO 在 &lt;code&gt;excludes&lt;/code&gt; 中排除了，但远程下发配置里标记的是本地加载，导致运行时找不到。&lt;/p&gt;
&lt;h3&gt;废弃 SO&lt;/h3&gt;
&lt;p&gt;最好的修复是移除。实际适配中发现了多个无代码引用或功能已废弃的 SO，直接删除既解决了 16KB 问题，又减小了包体积。比如我们项目中有几个早期接入的安全加固组件，服务端确认相关功能字段已经废弃后，连同 SO、混淆规则、初始化代码一起清理掉了。&lt;/p&gt;
&lt;h3&gt;短期无法获得兼容版本的 SDK&lt;/h3&gt;
&lt;p&gt;最棘手的情况。如果 SDK 方无法及时提供 16KB 版本，需要评估该 SO 加载失败对 App 的影响范围，必要时添加 fallback 逻辑。比如我们对视频缩略图获取做了降级处理——当 native 库加载失败时，fallback 到 &lt;code&gt;MediaMetadataRetriever&lt;/code&gt; 的纯 Java 实现，避免整个功能不可用。&lt;/p&gt;
&lt;h2&gt;移除 SoLoader&lt;/h2&gt;
&lt;p&gt;许多使用 Facebook 开源库（Fresco、Litho 等）的项目通过 &lt;code&gt;SoLoader&lt;/code&gt; 加载 SO。SoLoader 在 16KB 模式下存在兼容性问题——它解压 SO 到应用私有目录时可能不满足对齐要求。&lt;/p&gt;
&lt;p&gt;我们的方案是彻底移除 SoLoader：升级 Fresco 到 3.x（不再强制依赖 SoLoader），所有 &lt;code&gt;SoLoader.loadLibrary()&lt;/code&gt; 替换为 &lt;code&gt;System.loadLibrary()&lt;/code&gt;，移除 &lt;code&gt;SoLoader.init()&lt;/code&gt; 初始化调用。&lt;/p&gt;
&lt;p&gt;注意移除后的副作用：如果之前依赖 SoLoader 的自动依赖解析来加载 &lt;code&gt;libc++_shared.so&lt;/code&gt;，移除后需要确保该 SO 被正确打包到 APK 中。我们项目的某个 flavor 就因为这个问题在 debug 构建时崩溃，排查发现是 STL 共享运行库没有被正确打包进去。&lt;/p&gt;
&lt;h2&gt;Fresco 1.x → 3.x&lt;/h2&gt;
&lt;p&gt;Fresco 大版本升级是这次适配中工作量最大的部分之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AnimationListener&lt;/code&gt; 接口签名变更：回调参数从 &lt;code&gt;AnimatedDrawable2&lt;/code&gt; 改为 &lt;code&gt;Drawable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImagePipeline.getMainFileCache()&lt;/code&gt; 被移除，磁盘缓存查找需改用 &lt;code&gt;getDiskCachesStoreSupplier()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImagePipelineNativeLoader.load()&lt;/code&gt; 不再需要手动调用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CircleProgressBarDrawable&lt;/code&gt; 等自定义 Drawable 与内置版本冲突，需移除自定义实现&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DefaultLifecycleObserver&lt;/code&gt; shim 类需移除，改用 AndroidX 内置版本&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;建议全局搜索 &lt;code&gt;AnimatedDrawable2&lt;/code&gt;、&lt;code&gt;getMainFileCache&lt;/code&gt;、&lt;code&gt;SoLoader&lt;/code&gt;、&lt;code&gt;ImagePipelineNativeLoader&lt;/code&gt; 等关键词逐一修复。&lt;/p&gt;
&lt;h2&gt;反射限制&lt;/h2&gt;
&lt;p&gt;Android 16 收紧了对系统属性的反射访问。如果代码或 SDK 中有通过 &lt;code&gt;SystemProperties.get(&quot;net.dns*&quot;)&lt;/code&gt; 获取 DNS 的逻辑，在 16KB 模式设备上会直接失败，改用公开 API：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ConnectivityManager cm &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (ConnectivityManager) context.&lt;/span&gt;&lt;span&gt;getSystemService&lt;/span&gt;&lt;span&gt;(Context.CONNECTIVITY_SERVICE);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Network network &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cm.&lt;/span&gt;&lt;span&gt;getActiveNetwork&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;LinkProperties props &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cm.&lt;/span&gt;&lt;span&gt;getLinkProperties&lt;/span&gt;&lt;span&gt;(network);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;List&amp;lt;&lt;/span&gt;&lt;span&gt;InetAddress&lt;/span&gt;&lt;span&gt;&amp;gt; dnsServers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; props.&lt;/span&gt;&lt;span&gt;getDnsServers&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;验证&lt;/h2&gt;
&lt;p&gt;适配完成后的验证分两部分。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SO 兼容性验证&lt;/strong&gt;：用 linker64 逐一加载 APK 中所有 arm64 SO，建立兼容性基线。按上述分类策略逐一处理不兼容 SO 后，再次全量验证。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;功能回归&lt;/strong&gt;：在 16KB 设备（Pixel 8/8a, Android 16）和普通设备上双端回归。重点覆盖 SO 密集的功能区域——视频播放/录制、直播推流与连麦、语音消息录制与播放、人脸认证、图片加载与缓存、数据持久化（数据库读写）、崩溃采集与热更新。这些场景背后都有 native 库在工作，是最容易出问题的地方。&lt;/p&gt;
&lt;p&gt;另外建议在 CI 中集成 SO 对齐检查，防止后续迭代引入不兼容的 SO。&lt;/p&gt;
&lt;h2&gt;顺带做的事&lt;/h2&gt;
&lt;p&gt;16KB 适配是一次被迫触及全量 SO 依赖的机会，借此我们也做了一些清理：建立了完整的 SO 依赖清单（来源、版本、Maven 坐标、传递链全部记录）、移除了多个功能已废弃的安全组件和 SDK、用 &lt;code&gt;resolutionStrategy.force&lt;/code&gt; 统一了传递依赖的版本、审查了远程下发配置确保 &lt;code&gt;excludes&lt;/code&gt; 和下发清单一致。&lt;/p&gt;
&lt;p&gt;整体来说，16KB 适配不只是一个编译参数的修改，而是一次对 App 全量 native 依赖的深度梳理。核心难点在于 SO 来源分散、验证依赖真机、不同 SO 需要不同处理策略、以及 Fresco 大版本升级带来的接口变更。涉及多个基础库仓库和业务模块的协同修改，发版也需要按依赖顺序分步推进。建议尽早启动，为 SDK 方升级和内部测试留出缓冲时间。&lt;/p&gt;</content:encoded></item><item><title>用 Cursor 做 Code Review</title><link>https://folay.top/zh/blog/cursor-code-review</link><guid isPermaLink="true">https://folay.top/zh/blog/cursor-code-review</guid><description>基于 MCP 协议实现的代码审查工具，让 Cursor 能够读取 GitHub/GitLab 的 PR/MR 并自动提交审查意见。</description><pubDate>Sun, 04 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Code Review 是开发流程中绑定的环节，但人工审查效率有限，容易遗漏问题。这篇文章介绍我写的一个工具，让 Cursor 能够直接读取 PR/MR、分析代码变更、生成审查意见并提交到对应平台。&lt;/p&gt;
&lt;p&gt;项目地址：&lt;a href=&quot;https://github.com/OldJii/code-review-mcp&quot;&gt;https://github.com/OldJii/code-review-mcp&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;p&gt;Cursor 本身不具备访问 GitHub/GitLab API 的能力，需要通过 MCP（Model Context Protocol）来扩展。MCP 是 Anthropic 推出的协议，允许 AI 模型与外部数据源和工具交互。&lt;/p&gt;
&lt;p&gt;整体思路是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;写一个 MCP Server，封装 GitHub/GitLab 的 API&lt;/li&gt;
&lt;li&gt;在 Cursor 中配置这个 MCP Server&lt;/li&gt;
&lt;li&gt;用 mdc 规则文件定义审查规范，约束 AI 的审查行为&lt;/li&gt;
&lt;li&gt;在 Cursor 中对话，让 AI 读取 PR、分析代码、提交评论&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;MCP Server&lt;/h2&gt;
&lt;p&gt;MCP Server 用 Python 实现，通过 stdio 与 Cursor 通信。核心是抽象出一个 &lt;code&gt;CodeReviewProvider&lt;/code&gt; 基类，GitHub 和 GitLab 各自实现具体逻辑。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CodeReviewProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ABC&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@abstractmethod&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_pr_info&lt;/span&gt;&lt;span&gt;(self, repo: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, pr_id: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;) -&amp;gt; Dict:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@abstractmethod&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_pr_changes&lt;/span&gt;&lt;span&gt;(self, repo: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, pr_id: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;) -&amp;gt; Dict:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@abstractmethod&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_inline_comment&lt;/span&gt;&lt;span&gt;(self, repo: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, pr_id: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;, file_path: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;line: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;, line_type: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, comment: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; Dict:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;@abstractmethod&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_pr_comment&lt;/span&gt;&lt;span&gt;(self, repo: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, pr_id: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;, comment: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; Dict:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;GitHub 和 GitLab 的 API 差异主要在两个地方：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;认证方式&lt;/strong&gt;：GitHub 用 Bearer Token，GitLab 用 PRIVATE-TOKEN Header&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;行内评论&lt;/strong&gt;：GitLab 需要计算 &lt;code&gt;line_code&lt;/code&gt;，GitHub 直接传行号&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Token 获取&lt;/h3&gt;
&lt;p&gt;为了简化配置，没有让用户手动填写 Token，而是复用 &lt;code&gt;gh&lt;/code&gt; 和 &lt;code&gt;glab&lt;/code&gt; 命令行工具的认证信息。&lt;/p&gt;
&lt;p&gt;GitHub 直接调用 &lt;code&gt;gh auth token&lt;/code&gt; 获取：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_get_token&lt;/span&gt;&lt;span&gt;(self) -&amp;gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; subprocess.run(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;gh&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;auth&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;token&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;capture_output&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; result.returncode &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; result.stdout.strip()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FileNotFoundError&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;GitLab 需要从 glab 的配置文件中解析：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_get_token&lt;/span&gt;&lt;span&gt;(self) -&amp;gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;config_paths &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Path.home() &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;.config&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;glab-cli&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;config.yml&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Path.home() &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Library&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Application Support&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;glab-cli&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;config.yml&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; config_path &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; config_paths:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; config_path.exists():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;open&lt;/span&gt;&lt;span&gt;(config_path, &lt;/span&gt;&lt;span&gt;&apos;r&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; f:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;content &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; f.read()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pattern &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rf&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;re.escape(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.host)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;:.*?token:\s*([^\s\n]+)&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;match &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.search(pattern, content, re.&lt;/span&gt;&lt;span&gt;DOTALL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; match:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; match.group(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;).strip()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这样用户只需要执行 &lt;code&gt;gh auth login&lt;/code&gt; 或 &lt;code&gt;glab auth login&lt;/code&gt;，MCP Server 就能自动获取 Token。&lt;/p&gt;
&lt;h3&gt;GitLab 行内评论&lt;/h3&gt;
&lt;p&gt;GitLab 的行内评论 API 比较麻烦，需要提供 &lt;code&gt;line_code&lt;/code&gt; 参数。这个参数的格式是 &lt;code&gt;{head_sha}_{old_line}_{new_line}&lt;/code&gt;，需要从 diff 中解析出来。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_find_line_code&lt;/span&gt;&lt;span&gt;(self, diff: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, target_line: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;, line_type: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, head_sha: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lines &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; diff.split(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;old_line &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new_line &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; line &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; lines:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; line.startswith(&lt;/span&gt;&lt;span&gt;&apos;@@&apos;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;match &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.match(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@@ -&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(\d&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)(?:&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\+&lt;/span&gt;&lt;span&gt;(\d&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)(?:&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;&lt;span&gt; @@&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, line)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; match:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;old_line &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(match.group(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new_line &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(match.group(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; line.startswith(&lt;/span&gt;&lt;span&gt;&apos;-&apos;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;old_line &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; line_type &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;old&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; old_line &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; target_line:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;head_sha&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;old_line&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;_&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; line.startswith(&lt;/span&gt;&lt;span&gt;&apos;+&apos;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new_line &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; line_type &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;new&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; new_line &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; target_line:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;head_sha&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;old_line&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;new_line&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;old_line &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new_line &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;核心逻辑是遍历 diff 内容，根据 &lt;code&gt;@@&lt;/code&gt; 行头解析起始行号，然后跟踪 old_line 和 new_line 的变化，找到目标行对应的 line_code。&lt;/p&gt;
&lt;h3&gt;MCP 工具&lt;/h3&gt;
&lt;p&gt;最终暴露给 Cursor 的工具有 6 个：&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;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;get_pr_info&lt;/code&gt;&lt;/td&gt;&lt;td&gt;获取 PR/MR 的标题、描述、分支等信息&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;get_pr_changes&lt;/code&gt;&lt;/td&gt;&lt;td&gt;获取代码变更，支持按文件类型过滤&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;extract_related_prs&lt;/code&gt;&lt;/td&gt;&lt;td&gt;从描述中提取关联的 PR 链接&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;add_inline_comment&lt;/code&gt;&lt;/td&gt;&lt;td&gt;添加行内评论&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;add_pr_comment&lt;/code&gt;&lt;/td&gt;&lt;td&gt;添加整体评论&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;batch_add_comments&lt;/code&gt;&lt;/td&gt;&lt;td&gt;批量添加评论&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;batch_add_comments&lt;/code&gt; 是为了减少用户确认次数，一次性提交所有评论。&lt;/p&gt;
&lt;h2&gt;审查规范&lt;/h2&gt;
&lt;p&gt;MCP 只解决了”能做什么”的问题，“怎么做”需要通过 mdc 规则文件来约束。&lt;/p&gt;
&lt;p&gt;mdc 是 Cursor 的规则文件格式，可以在项目的 &lt;code&gt;.cursor/rules/&lt;/code&gt; 目录下定义。AI 在对话时会参考这些规则。&lt;/p&gt;
&lt;h3&gt;核心原则&lt;/h3&gt;
&lt;p&gt;审查规范的核心是”理解优先”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先理解 PR 整体目的，再看具体 diff&lt;/li&gt;
&lt;li&gt;深度分析（架构/逻辑/性能）优先于风格检查&lt;/li&gt;
&lt;li&gt;只提”确认的问题”，不提”可能的问题”&lt;/li&gt;
&lt;li&gt;结合上下文分析，不孤立判断&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后一条很重要。AI 容易犯的错误是看到一个变量可能为空就报 NPE 风险，但实际上调用方可能已经做过校验。规则里明确要求 AI 追溯上下文，避免误报。&lt;/p&gt;
&lt;h3&gt;审查流程&lt;/h3&gt;
&lt;p&gt;规则里定义了完整的审查流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;获取变更&lt;/strong&gt;：调用 &lt;code&gt;get_pr_changes&lt;/code&gt; 获取所有文件的完整 diff&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理解上下文&lt;/strong&gt;：从标题、描述、变更列表理解 PR 目的&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;li&gt;&lt;strong&gt;批量提交&lt;/strong&gt;：调用 &lt;code&gt;batch_add_comments&lt;/code&gt; 一次性提交&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第 5 步是关键。AI 生成的评论需要人工把关，避免提交低质量或错误的评论。&lt;/p&gt;
&lt;h3&gt;优先级&lt;/h3&gt;
&lt;p&gt;问题按严重程度分为三级：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;P0&lt;/strong&gt;：崩溃、内存泄漏、严重性能问题、安全漏洞&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P1&lt;/strong&gt;：架构问题、逻辑缺陷、代码质量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P2&lt;/strong&gt;：代码复用、命名优化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;P0 问题不限数量，P1 和 P2 会根据 P0 的数量动态调整。如果 P0 超过 10 个，说明代码质量较差，此时减少 P1/P2 的数量，集中精力在严重问题上。&lt;/p&gt;
&lt;h3&gt;避免误报&lt;/h3&gt;
&lt;p&gt;规则里列举了常见的误报场景：&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;NPE&lt;/td&gt;&lt;td&gt;调用方已校验&lt;/td&gt;&lt;td&gt;不提&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;越界&lt;/td&gt;&lt;td&gt;上文已检查长度&lt;/td&gt;&lt;td&gt;不提&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;性能&lt;/td&gt;&lt;td&gt;数据量 &amp;lt;= 5&lt;/td&gt;&lt;td&gt;不提&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;线程&lt;/td&gt;&lt;td&gt;耗时 &amp;lt; 1ms&lt;/td&gt;&lt;td&gt;不提&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这些规则是从实际 Review 经验中总结出来的。AI 的判断倾向于保守，容易对一些不是问题的代码报警。通过这些规则可以让 AI 更准确地识别真正的问题。&lt;/p&gt;
&lt;h2&gt;使用&lt;/h2&gt;
&lt;h3&gt;配置&lt;/h3&gt;
&lt;p&gt;MCP 配置是全局的，编辑 &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;code-review&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;command&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;python3&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;args&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;/path/to/cursor-ai-code-review/code_review_mcp.py&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;审查规范是项目级别的，需要复制到每个项目的 &lt;code&gt;.cursor/rules/&lt;/code&gt; 目录。这个设计是因为不同项目可能有不同的审查标准。&lt;/p&gt;
&lt;h3&gt;审查&lt;/h3&gt;
&lt;p&gt;配置完成后，在 Cursor 中直接输入：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Review https://github.com/owner/repo/pull/123&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;AI 会自动调用 MCP 工具获取 PR 信息和代码变更，然后按照规则进行审查，审查完成后会展示所有评论供确认：&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/image-20260104161855496.png&quot; alt=&quot;&quot; loading=&quot;eager&quot; /&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;我删除了某 Flutter 项目中 一个Dart 文件的一行 import 代码，用于演示工具的使用流程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;确认无误后，AI 会调用 &lt;code&gt;batch_add_comments&lt;/code&gt; 批量提交评论到 GitHub/GitLab。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/image-20260104161734500.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;h3&gt;私有 GitLab&lt;/h3&gt;
&lt;p&gt;如果使用私有化部署的 GitLab，在认证时指定地址：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;glab&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--hostname&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gitlab.yourcompany.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;MCP Server 会自动从 glab 配置中读取对应的 Token。&lt;/p&gt;
&lt;h2&gt;局限&lt;/h2&gt;
&lt;p&gt;这个工具目前还有一些局限：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;上下文有限&lt;/strong&gt;：AI 只能看到 diff，无法读取完整的项目代码。对于涉及多文件交互的问题，分析能力有限。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;规则依赖&lt;/strong&gt;：审查质量很大程度取决于 mdc 规则的完善程度。规则写得不好，AI 可能遗漏问题或产生误报。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要人工确认&lt;/strong&gt;：每次提交评论前需要人工确认，无法完全自动化。这是有意为之，避免 AI 提交低质量评论。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;后续可以考虑接入更多上下文，比如让 AI 能够读取相关文件、查看历史提交等。规则也可以根据实际使用情况持续优化。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;这个工具的核心价值是把 Code Review 的机械性工作交给 AI，人只需要确认最终结果。实际使用下来，对于常规的代码问题（空指针、资源泄漏、性能隐患等），AI 的识别率还是可以的。&lt;/p&gt;
&lt;p&gt;但 AI 不能完全替代人工 Review。架构设计、业务逻辑这些需要深度理解上下文的问题，还是得靠人来判断。把 AI 当作辅助工具，提高效率的同时保持人的把关，是目前比较务实的用法。&lt;/p&gt;</content:encoded></item><item><title>Cursor × Figma 技术调研</title><link>https://folay.top/zh/blog/figma-mcp</link><guid isPermaLink="true">https://folay.top/zh/blog/figma-mcp</guid><description>探索将 Figma MCP 融入到 Cursor Ai Coding WorkFlow 中的可行性。</description><pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;探索让 AI 直接读取 Figma 设计稿生成 UI 代码的可行性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：技术上可行，但在传统 XML 项目中还原度不高。调研发现，&lt;strong&gt;Jetpack Compose + Auto Layout&lt;/strong&gt; 才是最佳组合，要落地需要设计和开发一起推动技术栈升级。&lt;/p&gt;
&lt;h2&gt;Figma MCP&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MCP（Model Context Protocol）&lt;/strong&gt; 是让 AI 模型能够与外部数据源和工具交互的标准协议。&lt;code&gt;cursor-talk-to-figma-mcp&lt;/code&gt; 基于 MCP 协议，在 Cursor 和 Figma 之间建立连接，可以在 Cursor 里直接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;读取&lt;/strong&gt; Figma 设计稿的完整信息（图层、样式、布局、组件等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编辑&lt;/strong&gt; Figma 文件（创建图形、修改属性、调整布局）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成代码&lt;/strong&gt;：根据设计稿生成对应的 UI 代码&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;配置&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;：打开 Figma 桌面端，搜索 &lt;strong&gt;Cursor Talk To Figma MCP Plugin&lt;/strong&gt;，点击安装。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/image-20251211192011665.png&quot; width=&quot;500&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;：插件和 Cursor 之间需要一个中转服务来通信，打开终端运行：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bunx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor-talk-to-figma-socket&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;服务启动后会在本地监听端口，保持终端窗口不要关。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/image-20251211192333065.png&quot; width=&quot;450&quot; /&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;如果没有安装 bun，可以先用 &lt;code&gt;npm install -g bun&lt;/code&gt; 安装。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;：在任意 Figma 文件里打开插件面板，可以看到 Channel ID 和 MCP 配置信息。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/202512120049295.png&quot; width=&quot;350&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;：把配置复制到 Cursor 的 &lt;code&gt;mcp.json&lt;/code&gt; 文件中。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/image-20251211192639881.png&quot; width=&quot;700&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;配置完成后，在 Cursor 的 Chat 里就能直接和 Figma 设计稿交互了。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/image-20251211192852649.png&quot; width=&quot;700&quot; /&gt;
&lt;/div&gt;
&lt;h2&gt;权限问题&lt;/h2&gt;
&lt;p&gt;配置过程顺利，但使用时踩了个坑：&lt;strong&gt;企业账号没有编辑权限&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Figma 的账号体系有两个维度：&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;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;账号类型&lt;/td&gt;&lt;td&gt;Starter（免费）、Professional、Organization、Enterprise&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;席位类型&lt;/td&gt;&lt;td&gt;Full Seat、Dev Seat、Collab Seat、View Seat&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;公司用的是 Enterprise 账号，给开发分配的是 View Seat（只读席位），意味着在公司 Figma 里只能看不能编辑，而 Figma 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;公司 Enterprise 账号&lt;/td&gt;&lt;td&gt;View Seat&lt;/td&gt;&lt;td&gt;不行，Drafts 和 Projects 都没有编辑权限&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;个人 Starter 免费账号&lt;/td&gt;&lt;td&gt;Full Seat&lt;/td&gt;&lt;td&gt;可以，Drafts 里有完整权限&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;解决办法：&lt;strong&gt;把设计稿复制到个人账号里&lt;/strong&gt;。在公司 Figma 里打开目标设计稿，选中需要的 Frame，&lt;code&gt;Cmd + C&lt;/code&gt; 复制，切换到个人账号，在 Drafts 里新建文件，&lt;code&gt;Cmd + V&lt;/code&gt; 粘贴进去。&lt;/p&gt;
&lt;h2&gt;还原度&lt;/h2&gt;
&lt;p&gt;配置完成后，尝试用 Cursor 根据设计稿生成对应 &lt;strong&gt;XML 布局&lt;/strong&gt; 文件。结果是能用，但还原度一般，生成的代码需要大量调整才能用。&lt;/p&gt;
&lt;h3&gt;问题 1：几何式布局缺乏语义&lt;/h3&gt;
&lt;p&gt;传统 Figma 布局是&lt;strong&gt;几何式&lt;/strong&gt;的，设计师通过绝对坐标定位元素：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Text A → (x: 20, y: 50)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Text B → (x: 20, y: 80)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;AI 拿到这些数据后，只知道两个文本框垂直相距 30px，但无法判断设计师的意图：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这是一个垂直列表，间距固定 30px？&lt;/li&gt;
&lt;li&gt;这是两个独立元素，碰巧对齐了？&lt;/li&gt;
&lt;li&gt;这是某个列表项的一部分，设计师手动复制出来的？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 只能靠启发式算法猜测。猜错了布局就会有问题，猜测过程本身也消耗算力，影响生成速度和准确度。&lt;/p&gt;
&lt;h3&gt;问题 2：XML 与声明式设计不匹配&lt;/h3&gt;
&lt;p&gt;即使设计稿用了 Figma 的 &lt;strong&gt;Auto Layout&lt;/strong&gt;（一种声明式的布局方式，类似前端 Flexbox），生成 XML 代码的效果也不理想。&lt;/p&gt;
&lt;p&gt;根本原因是：&lt;strong&gt;Auto Layout 是声明式的，而 XML 布局是命令式的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Auto Layout 明确标注「这是一个垂直容器，子元素间距 30px」，但转成 XML 时，AI 需要把这些声明式规则翻译成 &lt;code&gt;LinearLayout&lt;/code&gt; + &lt;code&gt;layout_marginTop&lt;/code&gt; 等命令式属性，这个转换过程会损失很多信息，也容易出错。&lt;/p&gt;
&lt;h3&gt;调研结论&lt;/h3&gt;
&lt;p&gt;调研后发现，&lt;strong&gt;Jetpack Compose + Auto Layout&lt;/strong&gt; 才是最佳组合。&lt;/p&gt;
&lt;p&gt;Compose 是声明式 UI 框架，它的布局理念和 Auto Layout 天然契合。对应关系如下：&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Figma Auto Layout 属性&lt;/th&gt;&lt;th&gt;Jetpack Compose 实现&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Horizontal（水平排列）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Row { }&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Vertical（垂直排列）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Column { }&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;无 Auto Layout / Group&lt;/td&gt;&lt;td&gt;&lt;code&gt;Box { }&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fill Container（填充容器）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Modifier.fillMaxWidth()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hug Contents（包裹内容）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Modifier.wrapContentSize()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fixed Size（固定尺寸）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Modifier.size(dp)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Padding（内边距）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Modifier.padding()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Space between items（间距）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Arrangement.spacedBy()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Alignment（对齐）&lt;/td&gt;&lt;td&gt;&lt;code&gt;Arrangement.Center&lt;/code&gt; / &lt;code&gt;Alignment.*&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;几乎是一一对应。AI 拿到 Auto Layout 数据后，不用猜测和转换，直接映射成 Compose 代码即可。&lt;/p&gt;
&lt;h2&gt;当前价值&lt;/h2&gt;
&lt;p&gt;虽然在传统 XML 项目中无法完美还原 UI，但 Figma MCP 现阶段仍然有价值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;提取设计参数&lt;/strong&gt;：快速获取颜色值、字号、间距、圆角等，不用手动量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理解页面结构&lt;/strong&gt;：读取图层层级和命名，帮助理清组件划分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成骨架代码&lt;/strong&gt;：生成大致的布局框架，在此基础上微调&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;辅助设计走查&lt;/strong&gt;：对比设计稿和实现，快速定位差异&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把它当作辅助工具，而不是完全自动化的解决方案。&lt;/p&gt;
&lt;h2&gt;升级路径&lt;/h2&gt;
&lt;p&gt;要真正发挥 AI 设计转代码的能力，需要从两个方向推进：&lt;/p&gt;
&lt;h3&gt;设计侧&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;全面使用 Auto Layout&lt;/strong&gt;：所有组件都用声明式方式组织，而不是绝对定位&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;建立 Design System&lt;/strong&gt;：统一的设计组件库，命名规范化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组件化思维&lt;/strong&gt;：设计时就按开发的组件粒度拆分&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;开发侧&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;迁移到 Jetpack Compose&lt;/strong&gt;：声明式 UI 框架，和 Auto Layout 理念一致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;搭配 KMP（Kotlin Multiplatform）&lt;/strong&gt;：一套 Compose UI 代码，Android 和 iOS 都能用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实现对应组件库&lt;/strong&gt;：Figma 里的组件和代码里的组件一一对应&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当设计和开发都采用声明式体系，Figma 里的组件和代码里的组件形成映射关系后，AI 只需要做「翻译」工作：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Figma Auto Layout (声明式设计)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓ AI 直接映射&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Jetpack Compose (声明式代码)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这时候，设计师在 Figma 里调整一个按钮的圆角和间距，开发在 Cursor 里让 AI 同步更新代码，几秒钟就能完成，效率提升会非常明显。&lt;/p&gt;
&lt;p&gt;理想的技术栈是：&lt;strong&gt;Cursor + Figma MCP + Jetpack Compose + KMP + Auto Layout&lt;/strong&gt;。&lt;/p&gt;</content:encoded></item><item><title>桂林、龙胜、阳朔</title><link>https://folay.top/zh/blog/guilin</link><guid isPermaLink="true">https://folay.top/zh/blog/guilin</guid><description>第一次独自旅行。</description><pubDate>Tue, 21 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我一直偏爱山水风光，从小就听闻“桂林山水甲天下”，这次终于借着假期踏上了旅程。这是我的第一次独自旅行，7天里逛了不少地方。出发前在小红书刷到很多帖子说“到阳朔一天就想回桂林市区”，大多吐槽阳朔坑人，连本地人都不放过。但自己亲身走了一趟后发现，实际情况和网上的描述不太一样。&lt;/p&gt;
&lt;p&gt;先说说出行方式，桂林市区推荐骑共享电动车加打车的组合；阳朔的话，直接在住的酒店租电动车就好，一般不用交押金，外面租要收押金。建议选大电瓶的，虽然贵一点，但续航远，跑景点更省心。&lt;/p&gt;
&lt;p&gt;如果纯主观给这些天逛过的景点分个梯队，第一梯队是漓江三星游船、遇龙河漂流、千里江山图日落打卡地和阳朔骑电车闲逛；第二梯队是桂海晴岚、龙脊梯田；像正阳步行街、象山景区、日月双塔这些，我觉得没必要特意去。不是说这些地方不好，主要是同质化太严重，可能你家乡就有类似的景点，没必要专门跑一趟桂林看。&lt;/p&gt;
&lt;h3&gt;桂海晴岚&lt;/h3&gt;
&lt;p&gt;桂海晴岚在桂林市区比较边缘的位置，但桂林市区本身就不大，加上共享电动车很普及，去起来也不算麻烦。我订的酒店在两江四湖景区附近，骑电车到桂海晴岚大概25分钟。不过要注意，离景区还有1、2公里的时候，就超出共享电动车的服务范围了。边界那里有当地阿姨做代驾，花5块钱就能把你送到景点门口，很便宜。&lt;/p&gt;
&lt;p&gt;整个景区就像一个风景超棒的高尔夫球场，大片的草坪蔓延开，湖水清澈，远处是连绵的山峰，置身其中感觉像在童话世界里。景区面积很大，可以慢慢逛，找个喜欢的地方躺在草坪上，看着蓝天和湖水连在一起，放空自己，特别舒服。唯一的缺点是温度，10月份的桂林，在这种空旷的草坪上会被太阳晒得厉害，直到下午4点以后，温度才会稍微降下来，舒服一点。景区里的天鹅一点都不怕人，会在湖边悠闲地活动。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/E3FA6C6A-E764-4995-BD09-D612E85973B6_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;eager&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/15E6C0D6-02CF-4760-BC12-DC01FC090AF3_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/9C37544F-5309-4871-AA67-6C13AD073B13_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/0E84D0F6-477A-4737-B95E-52460EF6378F_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/4D042745-23C0-4689-BEEF-82FF2A098A36_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;龙脊梯田&lt;/h3&gt;
&lt;p&gt;龙脊梯田离市区很远，推荐报一日团当天往返。选团的时候要注意，别选包含长发村的，长发村完全没必要去。也不建议在山上过夜，山上没什么好吃的，能做的无非就是看景色、拍写真、吃饭这三件事。我没拍写真，山上的饭也不推荐，网上很火的竹筒饭味道一般，如果报的团有赠送可以尝一尝，没必要主动买，还是回桂林市区再吃比较好。&lt;/p&gt;
&lt;p&gt;龙脊梯田的景色确实很好看，但可看的内容比较少，看几眼就差不多了，不过不来看看又会觉得可惜。建议坐索道上山，步行下山。下山的路基础设施不太好，有很长一段路是和机动车同路的，没有专门的人行道，走路的时候一定要注意安全。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/9243ACFA-78E8-472D-8A0E-01897D826B95_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/75623328-5AF5-49E5-84A0-936E1186A056_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/A9FC9DF9-5599-42C4-B3A3-9ADBE5368CA8_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/40A2AAD9-5F5C-4E16-821A-2DA3E4149D49_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/E00394E1-2DF2-4FAF-863A-BC34D4C08494_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/79005806-2E2A-4AC3-BA1A-F1C3EC31B04D_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;漓江&lt;/h3&gt;
&lt;p&gt;从桂林去阳朔的方式，我选择的是乘坐漓江三星游船，这趟行程不只是交通，更是我心中的第一梯队的体验。航线是从桂林市区的磨盘山码头坐到阳朔的龙头山码头，班次时间是固定的，最晚一班是12:05。景区有直通车能送码头，但不会到酒店门口接，只会给个附近的集合点，不太方便，建议还是打车自己去。我坐的就是中午这班最晚的，需要提前半小时到码头，全程航行大概4小时。沿途会经过冠岩、杨堤、九马画山、黄布倒影这些经典景点，船舱里有讲解员实时介绍。船上有餐食，但比较简单，建议下船后再找地方吃。下船后，码头出口有不少骑电动车拉客的师傅，建议找一位带自己去酒店，因为要走挺远才能打到车。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/B7EF858E-A4CC-4094-B55C-9EC65BB2DC63_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/D0A09EF0-0671-48F2-9875-C1AB6D77FB86_4_5005_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/2B713E26-1DF5-4E65-87C8-14AA33A1CB02_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1173BE05-09A8-4901-88E8-3A5A6A5DECE2_4_5005_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;遇龙河&lt;/h3&gt;
&lt;p&gt;可能有人会好奇，为什么我把“阳朔骑电车闲逛”归到第一梯队里，等你真到了阳朔就会明白。阳朔的美不分景区、不分地点，骑上电车沿着乡间小路走，沿途随处都是像山水画一样的风景。如果说漓江是大气磅礴的，那遇龙河就是精巧雅致的，很符合传统山水意境里对“水”的想象。&lt;/p&gt;
&lt;p&gt;我最推荐的就是沿遇龙河的骑行路线：富里桥 → 遇龙桥 → 旧县码头 → 骥马码头 → 水厄底码头。把电车停在水厄底码头，就能去坐从这里到综合码头的竹筏漂流，漂流结束有摆渡车送回水厄底码头，之后还能继续骑到竹蔸寨、万景码头。这条路线能把遇龙河流域的精华景色都逛到，体验绝不比任何5A景区差。提醒一下，竹筏漂流是200元一筏，能坐两个人，独自旅行的话最好提前找好拼筏的人。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/D455E531-A6F6-443D-B350-2C7DEE4ECD64_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/B3A56E88-0833-46CB-8123-0103BDAC7540_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/522F14B2-9CE2-4CFC-8943-9A43C2AFE1B9_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/E2E0750A-FDA0-4CC5-8BE4-DCE57DA278B8_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;日落&lt;/h3&gt;
&lt;p&gt;7天的旅途中在很多地方看过日落，最惊艳的当属千里江山图的日落，这里的夕阳像是给连绵的山峦镀上了一层暖金，云曾被染成橘粉与绛紫交织的颜色，随着光线下沉，山峦的轮廓从清晰变得朦胧，宛如一幅水墨画，静谧且壮阔。&lt;/p&gt;
&lt;p&gt;简单说下交通，不推荐骑电车！30公里路程要骑1小时，景区在山上，爬坡费电，山顶充电电压低还慢，我就差点半路没电被困。而且回程国道多段没路灯，天黑后骑车极不安全，直接坐大巴或打车最省心。客观讲，这里可玩性不算高，景色也比较单一，但这份日落的惊艳，值得你专门跑一趟，不看真的会可惜。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/BE2B8D1D-CFC4-44B3-AE71-E6B7794E4A77.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/33B1644F-2EDB-4A7C-AD24-A1A01502D529.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/501741C3-7C76-4DED-9487-ADA505424E88.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/146A0A42-2259-43BB-8392-361647B223D8.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/0B8AD15D-40C0-4A1F-AD13-767DA10CAFF6.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/2394F6F8-F7D1-4F9A-956F-7E73E5E3BE84.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/D51ADC66-4687-435D-A31F-CE7224CC7828.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/620C41ED-A127-45F8-A25D-C8083347F430.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/779A1379-127F-4714-9B15-A25ABCF4BC9A.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;偶遇&lt;/h3&gt;
&lt;p&gt;比起计划好的行程，旅行中突然的偶遇更让人惊喜。我在小红书发了旅行帖子后，朋友看到问我是不是要去桂林，巧的是，他正好临时计划去阳朔野攀。我们一拍即合，我调整了后面两天的行程，去他们野攀的地方凑了热闹，还在朋友的指导下完成了我的第一次攀岩。&lt;/p&gt;
&lt;p&gt;很新奇的体验，攀岩的时候需要高度专注，能忘记很多烦心事。没有工作绩效的压力，也没有生活里的纷扰，眼里只有眼前的岩壁、指尖碰到的粗糙颗粒，还有每一步踩稳的着力点。那些压力和焦虑，好像都在这种极致的专注里被过滤得干干净净。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/image-20260103143106496.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/F0116CAA-7EC7-4189-8E57-24AFDACF1BE0_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/9CDCA937-DC67-41EA-BEC6-23393EF7CA47_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/DCDD1729-9AD1-4F36-9DE1-67877EFF940A_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;米粉&lt;/h3&gt;
&lt;p&gt;最后说说米粉，这几天在桂林，我每天至少吃一顿米粉。我本身就是粉面爱好者，所以怎么吃都不觉得腻。&lt;/p&gt;
&lt;p&gt;桂林市区和阳朔的米粉体验差别很大。市区的米粉好吃、便宜，花样还多；阳朔的米粉不管是价格还是味道，都没什么优势。市区里，三两的卤菜粉6元，一份锅烧5元，如果超过这个价格太多，就可以换一家店了。&lt;/p&gt;
&lt;p&gt;桂林米粉端上来的时候是干的，我问过本地人，正确的吃法是先干拌着吃，吃到剩下三分之一的时候再加汤，然后边喝汤边嗦粉。不过我个人更喜欢直接加汤，加到没过三分之一米粉的位置，再单独加一份锅烧，酸笋、酸萝卜、烧椒酱都多加一份，充分搅拌均匀后大口吃，很是过瘾。&lt;/p&gt;
&lt;p&gt;提醒一下，吃米粉的时候加辣椒要谨慎，先少放一点，觉得不够再慢慢加。我刚到桂林的第一晚，就因为没掌握好辣椒的辣度，被狠狠上了一课。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/0B329D0C-8C12-4D62-A2E9-9F95E3842D11_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/91F0BFB8-1A34-4E37-A2AE-8144AE89FF00_1_102_a.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/95F7D736-7083-4242-AFC5-3DA97E4B35B7_1_102_a.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/984E7CBE-CE5C-404E-B524-A512CE59AE74_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/6708F6A3-481C-4509-B873-BBADA061E6A9_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/A16A7E28-B322-40D6-8CF3-2A758000633C_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/D3C238F6-B74E-454D-806C-EC1EB2B88E1D_1_102_o.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;</content:encoded></item><item><title>Blog 迁移 Slate 的多图排版适配</title><link>https://folay.top/zh/blog/multiple_image_slate</link><guid isPermaLink="true">https://folay.top/zh/blog/multiple_image_slate</guid><description>从 Hugo Even 主题迁移到 Astro Slate 主题后，多图排版功能的重新适配方案，解决不同 HTML 结构下的分栏显示问题。</description><pubDate>Sun, 13 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;将博客从 &lt;a href=&quot;https://github.com/olOwOlo/hugo-theme-even&quot;&gt;Hugo Even 主题&lt;/a&gt; 迁移到了基于 Astro 的 &lt;a href=&quot;https://github.com/SlateDesign/slate-blog&quot;&gt;Slate 主题&lt;/a&gt;。在迁移过程中，发现原有的多图排版功能需要重新适配，因为新主题使用了不同的 HTML 结构。&lt;/p&gt;
&lt;p&gt;在 Hugo Even 主题中，Markdown 中的图片会被渲染为 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 标签包裹 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 标签的形式，而 Slate 主题则使用 &lt;code&gt;&amp;lt;div class=&quot;rehype-figure-container&quot;&amp;gt;&lt;/code&gt; 包裹 &lt;code&gt;&amp;lt;figure class=&quot;rehype-figure&quot;&amp;gt;&lt;/code&gt; 的结构。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 两张图片分栏 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 三张图片分栏 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 四张图片分栏 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 五张图片分栏 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 六张图片分栏 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 处理图片显示 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;.rehype-figure&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;.rehype-figure&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;.rehype-figure&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;.rehype-figure&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;.rehype-figure&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;block&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin-bottom&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;break-inside&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;avoid&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;block&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 响应式设计 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;768&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.rehype-figure-container:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.rehype-figure:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;column-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;使用规则与 Hugo Even 主题保持一致。图片之间可以换行，但不能空行，CSS 会根据容器内的图片数量自动调整分栏数量。支持 2-6 张图片的自动分栏。&lt;/p&gt;
&lt;p&gt;四张图片的演示效果：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.Umschreibung_EN-US4693850900_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;eager&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.HummockIce_EN-US4606231645_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.DonkeyFeast_EN-US1153850805_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.Breckenridge_EN-US4460042968_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;更多图片演示效果请查看 &lt;a href=&quot;https://www.folay.top/blog/multiple_image&quot;&gt;Hugo 多图排版 even 主题适配&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;将上述 CSS 代码添加到 Slate 主题的样式文件中。具体位置可能因主题配置而异，通常可以添加到 &lt;code&gt;src/assets/style/common.css&lt;/code&gt; 或者创建新的样式文件并在主题配置中引入。&lt;/p&gt;</content:encoded></item><item><title>App端内网络监控</title><link>https://folay.top/zh/blog/android_network</link><guid isPermaLink="true">https://folay.top/zh/blog/android_network</guid><description>Android应用端内网络监控方案，包括应用网速检查和DNS劫持检测，为网络问题定位提供结构化数据支持。</description><pubDate>Sat, 21 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;App 运行环境复杂，用户投诉中很大一部分集中在”页面加载慢”、“登录失败”、“连接不稳定”等网络相关问题上，这类问题在测试环境中往往难以复现。传统的服务端监控或 CDN 日志，无法完整还原终端侧的真实网络状态。&lt;/p&gt;
&lt;p&gt;针对这类问题，设计的端内网络监控方案核心目标是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主动感知用户当前的网络实际情况&lt;/li&gt;
&lt;li&gt;精准识别网络故障的真实成因&lt;/li&gt;
&lt;li&gt;为网络问题的定位、修复和优化提供结构化的数据支撑&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体实现为应用网速检查、DNS 劫持检测两部分。&lt;/p&gt;
&lt;h2&gt;应用网速检查&lt;/h2&gt;
&lt;h3&gt;原理&lt;/h3&gt;
&lt;p&gt;该模块基于 Android 原生的 &lt;code&gt;TrafficStats&lt;/code&gt; API，以应用 UID 为统计粒度，在固定的短时窗口（5 秒）内读取接收流量增量，通过增量字节数除以时间差计算客户端真实下载速度。该方式无需额外权限或发起网络请求，运行轻量化，且能反映用户真实的网络使用体验。&lt;/p&gt;
&lt;h3&gt;检查时机&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;应用启动时：首次启动立即执行检查&lt;/li&gt;
&lt;li&gt;网络切换时：例如从 WiFi 切换至移动网络&lt;/li&gt;
&lt;li&gt;应用回前台时：避免后台期间网络状态发生变化未被感知&lt;/li&gt;
&lt;li&gt;请求失败时：辅助诊断网络层面的问题诱因&lt;/li&gt;
&lt;li&gt;每 20 秒定期执行：满足长时间驻留场景下的持续采样需求&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于检测本身需要 5 秒的采样窗口，实际上天然具备了去重能力，无需额外设置冷却时间。同时，为避免多线程并发触发检查导致的统计异常，通过原子变量标记当前检查状态，确保同一时间仅执行一次检查。&lt;/p&gt;
&lt;h3&gt;数据上报&lt;/h3&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&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;/td&gt;&lt;td&gt;&lt;code&gt;32.00 KB/s&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;当前网络类型&lt;/td&gt;&lt;td&gt;&lt;code&gt;WIFI&lt;/code&gt; / &lt;code&gt;4G&lt;/code&gt; / &lt;code&gt;5G&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;运营商名称&lt;/td&gt;&lt;td&gt;&lt;code&gt;中国联通&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;移动网络信号强度&lt;/td&gt;&lt;td&gt;&lt;code&gt;-88&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;是否处于漫游状态&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;WiFi 信号等级&lt;/td&gt;&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;注：部分字段需要特定权限支持（获取信号强度/WiFi 等级需 &lt;code&gt;ACCESS_FINE_LOCATION&lt;/code&gt; 权限，获取漫游状态需 &lt;code&gt;READ_PHONE_STATE&lt;/code&gt; 权限），方案仅做权限判断不主动申请，若权限缺失则不上报对应字段。速度值会根据量级自动格式化为 B/s、KB/s 或 MB/s。&lt;/p&gt;
&lt;h2&gt;DNS 劫持检测&lt;/h2&gt;
&lt;h3&gt;原理&lt;/h3&gt;
&lt;p&gt;DNS 劫持是客户端常见的网络安全问题，在公共 WiFi 或部分三线运营商环境下更容易出现。我们通过对比系统 DNS 与可信 DoH 服务的解析结果，来识别是否存在劫持行为。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统 DNS：调用 &lt;code&gt;InetAddress.getAllByName()&lt;/code&gt; 获取解析结果&lt;/li&gt;
&lt;li&gt;DoH 服务：如 Google DNS、AliDNS、腾讯 DNSPod，通过加密连接获取的权威解析结果&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Google DNS：&lt;a href=&quot;https://dns.google/resolve?name=%25s&amp;amp;type=A&quot;&gt;https://dns.google/resolve?name=%s&amp;amp;type=A&lt;/a&gt;&lt;br /&gt;
AliDNS：&lt;a href=&quot;https://dns.alidns.com/resolve?name=%25s&amp;amp;type=A&quot;&gt;https://dns.alidns.com/resolve?name=%s&amp;amp;type=A&lt;/a&gt;&lt;br /&gt;
DNSPod：&lt;a href=&quot;https://doh.pub/dns-query?name=%25s&amp;amp;type=A&quot;&gt;https://doh.pub/dns-query?name=%s&amp;amp;type=A&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;可信 IP 池与缓存机制&lt;/h3&gt;
&lt;p&gt;可信 IP 池由两部分组成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;当前 DoH 解析结果&lt;/strong&gt;：本次从三家 DoH 服务获取到的 IP 地址&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;历史 DoH 缓存&lt;/strong&gt;：过去通过 DoH 成功解析过的 IP 地址，本地持久化存储，有效期 90 天&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;缓存采用结构化存储设计，核心包含”域名-IP-时间戳”的关联结构：以域名为维度归类 IP，每个 IP 条目附带最后更新时间戳，用于后续过期判断。对应的核心策略如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存更新策略：每次 DoH 解析成功后，将有效 IP 添加到对应域名的缓存中；若 IP 已存在，则更新其时间戳，确保活跃 IP 的有效性&lt;/li&gt;
&lt;li&gt;过期清理策略：定期过滤超出 90 天有效期的 IP 条目，避免缓存冗余占用终端存储，同时保证缓存 IP 的时效性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;判定逻辑执行前会先通过网络连接状态检查（确认设备是否真的接入网络），避免将网络未连接的情况误判为 DNS 问题。在此基础上，具体判断逻辑为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若系统 DNS 返回的 IP 全部包含在可信 IP 池中，视为解析正常&lt;/li&gt;
&lt;li&gt;若存在系统 DNS 返回的 IP 不在可信 IP 池中，判定为疑似劫持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种设计主要是考虑到 CDN 和负载均衡场景下，同一域名的解析结果会随时间、地域变化。单纯依赖实时 DoH 结果对比，容易将正常的 IP 轮转误判为劫持。通过维护历史 DoH 缓存，可以有效缓解这一问题。&lt;/p&gt;
&lt;h3&gt;检查时机&lt;/h3&gt;
&lt;p&gt;为节省终端资源，DNS 劫持检查仅在网络请求失败且命中特定异常类型集合时触发，同时设置 5 秒冷却时间，即两次检查之间至少间隔 5 秒。该冷却机制的核心作用是避免同一网络问题（如持续的连接失败）导致频繁触发检测：一方面可减少重复的 DoH 网络请求，降低用户流量消耗；另一方面能避免终端线程频繁占用，提升应用运行流畅度。&lt;/p&gt;
&lt;p&gt;相较于启动、切网等高频触发方式，这种按需触发+精准异常命中的策略更贴合实际业务场景，也能减少不必要的资源消耗。&lt;/p&gt;
&lt;h3&gt;异常类型集&lt;/h3&gt;
&lt;p&gt;以下是网络异常中与 DNS 劫持高度相关的典型类型：&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;code&gt;CertPathValidatorException&lt;/code&gt;&lt;/td&gt;&lt;td&gt;证书不可信&lt;/td&gt;&lt;td&gt;TLS 证书签名链不受信任，常见于中间人攻击场景&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SSLHandshakeException&lt;/code&gt; / &lt;code&gt;SSLPeerUnverifiedException&lt;/code&gt;&lt;/td&gt;&lt;td&gt;TLS 握手失败&lt;/td&gt;&lt;td&gt;证书链异常或握手中断，可能由劫持导致&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;UnknownHostException&lt;/code&gt;&lt;/td&gt;&lt;td&gt;DNS 解析失败&lt;/td&gt;&lt;td&gt;无法将域名解析为 IP，多出现于 DNS 劫持或污染场景&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ConnectException&lt;/code&gt;&lt;/td&gt;&lt;td&gt;连接被拒绝&lt;/td&gt;&lt;td&gt;网络可达但目标端口无响应，可能是劫持后的伪地址&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SocketTimeoutException&lt;/code&gt;&lt;/td&gt;&lt;td&gt;TCP 超时&lt;/td&gt;&lt;td&gt;网络拥堵或服务不可达，需结合 DNS 解析结果排查&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;检测结果分类&lt;/h3&gt;
&lt;p&gt;根据系统 DNS 和 DoH 的解析情况，我们将检测结果细分为以下几类：&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;/td&gt;&lt;td&gt;系统 DNS 和 DoH 都成功，但系统 IP 不在可信 IP 池中&lt;/td&gt;&lt;td&gt;疑似 DNS 劫持&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;网络问题&lt;/td&gt;&lt;td&gt;检测到网络未连接&lt;/td&gt;&lt;td&gt;真实的网络连接中断&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;系统 DNS 失败&lt;/td&gt;&lt;td&gt;系统 DNS 失败但 DoH 成功&lt;/td&gt;&lt;td&gt;本地 DNS 配置问题&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DoH 失败&lt;/td&gt;&lt;td&gt;系统 DNS 成功但 DoH 失败&lt;/td&gt;&lt;td&gt;DoH 服务不可用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;正常&lt;/td&gt;&lt;td&gt;系统 DNS 的 IP 全部在可信 IP 池中&lt;/td&gt;&lt;td&gt;解析结果一致&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;未知&lt;/td&gt;&lt;td&gt;网络连接正常但 DNS 服务均失败&lt;/td&gt;&lt;td&gt;DNS 服务器问题或端口被封锁&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这种细粒度的分类有助于快速定位问题根因，避免将所有异常都归结为”劫持”。&lt;/p&gt;
&lt;h3&gt;数据上报&lt;/h3&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&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;/td&gt;&lt;td&gt;&lt;code&gt;api.example.com&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;系统 DNS 返回的 IP 列表&lt;/td&gt;&lt;td&gt;&lt;code&gt;[1.2.3.4, 5.6.7.8]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DoH 返回的 IP 汇总&lt;/td&gt;&lt;td&gt;&lt;code&gt;GoogleDNS=[1.2.3.4]; AliDNS=[1.2.3.4, 9.9.9.9]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;检测结果状态&lt;/td&gt;&lt;td&gt;&lt;code&gt;IS_HIJACKED&lt;/code&gt; / &lt;code&gt;PASS&lt;/code&gt; / &lt;code&gt;NETWORK_FAIL&lt;/code&gt; 等&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;详细原因描述&lt;/td&gt;&lt;td&gt;包含异常 IP、可信 IP 池等详细信息&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;触发检查的异常类型&lt;/td&gt;&lt;td&gt;&lt;code&gt;TLS_CERT_UNTRUSTED_SIGNATURE&lt;/code&gt; / &lt;code&gt;DNS_RESOLUTION_FAILED&lt;/code&gt; 等&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;注：为避免无效数据干扰分析，埋点仅上报判定为劫持的情况。&lt;/p&gt;</content:encoded></item><item><title>App认证链路设计</title><link>https://folay.top/zh/blog/cert</link><guid isPermaLink="true">https://folay.top/zh/blog/cert</guid><description>移动应用认证系统的分层架构设计，包括云端动态分配、多SDK统一封装和模块动态化等关键技术方案。</description><pubDate>Wed, 16 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在移动应用中，用户身份认证是保障账户安全、满足合规需求的关键模块。随着业务场景的复杂化（如从单一的头像认证到实名认证、账号找回等多场景）和技术方案的演进（如从集成单个 SDK 到动态调度多个第三方服务），如何构建一个高内聚、低耦合、可扩展的认证系统，成为了一项挑战。&lt;/p&gt;

&lt;h2&gt;宏观设计&lt;/h2&gt;
&lt;p&gt;认证系统的设计需要具备前瞻性，以灵活应对业务变化和技术选型更迭。为实现此目标，推荐采用分层的、服务化的架构，而非将认证逻辑直接耦合在业务代码中。&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;SDK 抽象层&lt;/strong&gt;：将所有第三方认证 SDK 的能力进行抽象，定义了统一的认证服务接口，涵盖了”初始化”、“启动认证”等原子能力。无论是服务商 A 的 SDK 还是服务商 B 的 SDK，都通过一个适配器实现这个统一接口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三方 SDK 层&lt;/strong&gt;：即各个服务商提供的原生 SDK。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;关键设计&lt;/h2&gt;
&lt;h3&gt;云端动态分配&lt;/h3&gt;
&lt;p&gt;具体使用哪个第三方 SDK，不由客户端硬编码决定，而是由业务服务器在下发认证凭证时动态指定。这种模式极大地提升了业务的灵活性和系统的容灾能力，同时方便服务端实现对不同认证服务商在成功率、耗时、费用等维度的&lt;strong&gt;A/B 测试&lt;/strong&gt;，用数据驱动决策。&lt;/p&gt;
&lt;h3&gt;多 SDK 统一封装与抽象&lt;/h3&gt;
&lt;p&gt;该设计的基石在于对多 SDK 的统一封装。通过定义统一的服务标准接口和统一的数据模型，将不同服务商 SDK 的差异性完全收敛在独立的适配器中。这使得上层业务逻辑完全与具体的 SDK 实现解耦，实现了认证能力的可插拔。&lt;/p&gt;
&lt;h3&gt;模块动态化&lt;/h3&gt;
&lt;p&gt;考虑到认证功能并非高频使用，且其依赖的库（尤其是底层库）体积较大，将整个认证模块设计为&lt;strong&gt;动态加载&lt;/strong&gt;。当用户首次触发认证流程时，客户端才通过动态模块加载器去加载。&lt;/p&gt;
&lt;h2&gt;认证流程&lt;/h2&gt;
&lt;div&gt;
sequenceDiagram
    participant 用户
    participant 业务应用层
    participant SDK抽象层
    participant 认证服务层
    participant 服务端
    用户-&amp;gt;&amp;gt;业务应用层: 触发认证
    业务应用层-&amp;gt;&amp;gt;业务应用层: 1. 业务认证场景
    业务应用层-&amp;gt;&amp;gt;业务应用层: 2. 构建标准化参数
    业务应用层-&amp;gt;&amp;gt;SDK抽象层: 请求调起认证 (带参数)
    SDK抽象层-&amp;gt;&amp;gt;SDK抽象层: 6. SDK初始化
    SDK抽象层-&amp;gt;&amp;gt;用户: 7. 调起认证界面
    用户-&amp;gt;&amp;gt;认证服务层: 8. 执行操作 (例如输入凭证)
    认证服务层-&amp;gt;&amp;gt;认证服务层: 9. 本地预处理
    认证服务层-&amp;gt;&amp;gt;服务端: 请求获取/验证云端凭证
    服务端--&amp;gt;&amp;gt;认证服务层: 返回云端凭证/验证结果
    认证服务层-&amp;gt;&amp;gt;认证服务层: 3. 获取云端凭证 (完成)
    认证服务层-&amp;gt;&amp;gt;认证服务层: 4. 动态加载SO库
    认证服务层-&amp;gt;&amp;gt;认证服务层: 5. 权限校验
    认证服务层-&amp;gt;&amp;gt;服务端: 10. 上报认证结果
    服务端--&amp;gt;&amp;gt;认证服务层: 确认上报
    认证服务层-&amp;gt;&amp;gt;SDK抽象层: 返回认证结果
    SDK抽象层-&amp;gt;&amp;gt;业务应用层: 返回认证结果
    业务应用层-&amp;gt;&amp;gt;用户: 显示认证结果
&lt;/div&gt;</content:encoded></item><item><title>Hugo 多图排版 even 主题适配</title><link>https://folay.top/zh/blog/multiple_image</link><guid isPermaLink="true">https://folay.top/zh/blog/multiple_image</guid><description>Hugo Even主题多图排版CSS适配方案，解决竖图和横图混合排版时的空白问题，实现自动分栏效果。</description><pubDate>Mon, 24 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.Umschreibung_EN-US4693850900_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;eager&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.HummockIce_EN-US4606231645_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.DonkeyFeast_EN-US1153850805_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.Breckenridge_EN-US4460042968_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;

&lt;p&gt;在使用 Hugo 搭建个人博客时，常常会遇到图片排版的问题。尤其是竖图和横图混合时，Hugo 默认的排版方式会让竖图左右留出大量空白，显得不美观。此外，Markdown 本身也不支持横向排列图片（除非使用内嵌 HTML，但这种方式存在诸多问题，我尝试后已放弃）。&lt;/p&gt;
&lt;p&gt;之前我参考了一篇 &lt;a href=&quot;https://immmmm.com/about-images-gird/&quot;&gt;Hugo 多图排版的教程&lt;/a&gt;，但按照教程操作后并没有达到预期效果。评论区里也有其他用户遇到了类似问题，但原作者并未给出解答。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503251526936.png&quot; alt=&quot;image-20250325152613877&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;image-20250325152613877&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;经过一番检查和尝试，我发现问题出在主题生成静态页面时，会将 Markdown 中的图片用 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 标签包裹，以便实现点击预览大图的效果。这与原教程的 CSS 选择器不匹配，导致样式无法生效。下面是适配方案。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 多图分栏样式 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.post-content&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;column-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.post-content&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)) { &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.post-content&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)) { &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.post-content&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)) { &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.post-content&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)) { &lt;/span&gt;&lt;span&gt;column-count&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 处理图片显示 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.post-content&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;block&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin-bottom&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;break-inside&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;avoid&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 针对6图模式调整间距 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.post-content&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;:has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;margin-bottom&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* 确保图片填充容器 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.post-content&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;block&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;对于 Even 主题，需要将上述代码追加到 &lt;code&gt;Blog/themes/even/assets/sass/_partial/_post/_content.scss&lt;/code&gt; 文件中。请注意，不同 Hugo 主题的 &lt;code&gt;content&lt;/code&gt; 文件目录可能会有所不同，具体路径需要根据你所使用的主题进行调整。&lt;/p&gt;
&lt;h3&gt;使用规则&lt;/h3&gt;
&lt;p&gt;使用规则与原教程一致。图片之间可以换行，但不能空行，这样 CSS 会根据 &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; 元素内的 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 标签数量自动调整分栏数量。例如：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#### 更多图片横竖随机&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.Breckenridge_EN-US4460042968_768x1280.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.BisonWindCave_EN-US4537340482_1280x768.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.Umschreibung_EN-US4693850900_768x1280.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.DonkeyFeast_EN-US1153850805_768x1280.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.SessileOaks_EN-US1487454928_1280x768.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.DonkeyFeast_EN-US1153850805_1280x768.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.RumeliHisari_EN-US4800002879_1280x768.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.InscriptionWall_EN-US1392173431_1280x768.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;![](&lt;/span&gt;&lt;span&gt;https://cn.bing.com/th?id=OHR.HummockIce_EN-US4606231645_768x1280.jpg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;效果如下&lt;/h3&gt;
&lt;h4&gt;两张&lt;/h4&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.SessileOaks_EN-US1487454928_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.InscriptionWall_EN-US1392173431_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h4&gt;三张&lt;/h4&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.DonkeyFeast_EN-US1153850805_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.RumeliHisari_EN-US4800002879_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.BisonWindCave_EN-US4537340482_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h4&gt;四张&lt;/h4&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.Umschreibung_EN-US4693850900_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.HummockIce_EN-US4606231645_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.DonkeyFeast_EN-US1153850805_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.Breckenridge_EN-US4460042968_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h4&gt;更多图片横竖随机&lt;/h4&gt;
&lt;p&gt;同样会出现各列高度不一致的情况，但可以接受。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.Breckenridge_EN-US4460042968_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.BisonWindCave_EN-US4537340482_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.Umschreibung_EN-US4693850900_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.DonkeyFeast_EN-US1153850805_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.SessileOaks_EN-US1487454928_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.DonkeyFeast_EN-US1153850805_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.RumeliHisari_EN-US4800002879_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.InscriptionWall_EN-US1392173431_1280x768.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://cn.bing.com/th?id=OHR.HummockIce_EN-US4606231645_768x1280.jpg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;</content:encoded></item><item><title>贵州山水</title><link>https://folay.top/zh/blog/guizhou</link><guid isPermaLink="true">https://folay.top/zh/blog/guizhou</guid><description>贵州6日游记录，包括黄果树瀑布、荔波小七孔、镇远古镇和高过河漂流等景点的游览体验和美食分享。</description><pubDate>Fri, 20 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Day 1&lt;/h3&gt;
&lt;p&gt;下午落地贵阳，因为晚上要赶高铁去安顺，白天只安排了「&lt;strong&gt;长征文化数字艺术馆（红飘带）&lt;/strong&gt;」一个景点。从地铁站出来后，发现离目的地还有一段距离，门口倒是有共享电动车可以骑。第一次真实感受到贵州的山地地形——地铁站孤零零地建在高处，周围几百米都是空地，没有其他建筑，很贵州。&lt;/p&gt;
&lt;p&gt;艺术馆主要看了沉浸式宣传片《红飘带·多彩飞越》，用全息投影、3D Mapping这些技术把贵州的知名景点都展示了一遍（题外话：北京环球影城的变形金刚·火种源争夺战用的也是类似技术）。晚上吃了贵州酸汤火锅，味道直接惊艳到我，回北京后还特意找了几家连锁店去吃，可惜北京的店没有提前调好的蘸料，味道还是差了点。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202504011802530.png&quot; alt=&quot;&quot; loading=&quot;eager&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202504011800849.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202504011802736.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;Day 2&lt;/h3&gt;
&lt;p&gt;全天都在「&lt;strong&gt;黄果树瀑布&lt;/strong&gt;」逛，作为国内知名景点，名气确实大，但实际体验只能说一般。瀑布本身挺壮观的，水雾也大，但景区里的指示牌有点离谱——明明写着“前方20米观景台”，结果走了200米才到。步道设计有点复杂，容易走冤枉路，而且观光车还要额外收费，感觉不太厚道。第一次来贵州可以打个卡，但别抱太高期望，属于典型的“来都来了”景点。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503271815975.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503271814275.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503271815690.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;Day 3&lt;/h3&gt;
&lt;p&gt;如果要给这次贵州之旅的景点排个序，「&lt;strong&gt;荔波小七孔&lt;/strong&gt;」绝对是榜首。酒店直接订在景区旁边，时间很充裕。早上先去吃了小红书上很火的牛肉粉和刨冰，味道不错，就是牛肉有点难嚼，吃多了牙疼。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503271837304.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503271837543.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202504011758861.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;小七孔虽然要走不少路，但水是真的清澈，绿得像加了滤镜一样。最爽的是划透明船，水浅的地方能直接看到底，划到树荫下躺平，听着水流声，整个人都放空了，差点睡着。「&lt;strong&gt;大七孔&lt;/strong&gt;」是坐游船进去的，两边的山特别高，水特别静，和小七孔是完全不同的感觉，甚至有点江南水乡的韵味。总之，大小七孔都极度推荐！&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503271839616.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503271839402.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503271839282.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;Day 4&lt;/h3&gt;
&lt;p&gt;贵州古镇不少，因为第二天要去漂流，顺路选了「&lt;strong&gt;镇远古镇&lt;/strong&gt;」。这里还住着不少本地人，商业化不算太重，逛起来比较舒服。核心区不大，沿着舞阳河走一走，两三个小时就能逛完。老房子挺有味道，但住宿条件一般，不建议专程来，作为漂流的中转站刚好。&lt;/p&gt;
&lt;p&gt;晚上坐了夜游船，灯光映在河面上，两岸的老建筑倒影很美，夏夜凉风吹着特别惬意。晚餐吃了家开了十几年的红酸汤火锅，小盘菜自选，酸辣够味，比连锁店更地道。不过默认蘸料会加折耳根，不吃的记得提前说。整体来说，适合慢悠悠地逛，不用太赶时间。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503281610477.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503281610149.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503281617894.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;Day 5&lt;/h3&gt;
&lt;p&gt;「&lt;strong&gt;高过河漂流&lt;/strong&gt;」是这次旅行的主要目标，结果也没让人失望。因为是中秋假期，人比平时多，但排队还算快。两人一船，现场可以拼船（单身狗友好）。河水挺急的，但越急越爽，有几个坡道落差很大，冲下去的时候全身浸在水里，特别刺激。&lt;/p&gt;
&lt;p&gt;水枪战也是亮点，沿河一路都有卖水枪的，建议买带水管的那种，不用抽水，战力直接翻倍。不过要注意安全，因为翻船落水的情况几乎每天都有，我们也碰到了。虽然沿河都有救生员，但湍急的河段他们也不敢贸然下水，只能等你漂到水流平稳的地方才会施救。总的来说，超级好玩！强烈推荐！&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503281611473.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503281613746.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503281614184.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;Day 6&lt;/h3&gt;
&lt;p&gt;因为往返机票都是贵阳，所以提前一天返回贵阳休整，去了「&lt;strong&gt;青云集市&lt;/strong&gt;」和「&lt;strong&gt;黔灵山公园&lt;/strong&gt;」。青云集市就是一条改造过的商业街，小吃和文创店居多，和别的城市网红步行街差不多，没什么特色，个人不太感冒。黔灵山公园就是普通城市公园+很多猴子，也没网上说的那么好玩。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202504011811053.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202504011808638.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202504011808129.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/div&gt;</content:encoded></item><item><title>Thailand</title><link>https://folay.top/zh/blog/thailand</link><guid isPermaLink="true">https://folay.top/zh/blog/thailand</guid><description>泰国7日游记录，包括曼谷、芭提雅等地的景点游览、美食体验和泰拳比赛等精彩行程分享。</description><pubDate>Wed, 05 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最喜欢的泰拳🥊当封面&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503182347756.jpeg&quot; alt=&quot;&quot; loading=&quot;eager&quot; /&gt;&lt;/figure&gt;

&lt;h2&gt;准备&lt;/h2&gt;
&lt;p&gt;不用准备，提前买个电话卡带着护照直接冲！换汇什么的图便宜国内换，图方便机场换，其他都不是必需品，连衣物都不用多带，入乡随俗，现买现穿！&lt;/p&gt;
&lt;p&gt;地铁有点繁琐，出行还是打滴🚖方便，出租车软件有Bolt、Grab，Bolt是价格便宜，还有摩托车车型（有点危险，但很快很快很快，无视红绿灯那种），只支持现金支付。Grab司机接单的时间会短一些，但是价格也贵一些，还支持Alipay。&lt;/p&gt;
&lt;p&gt;另外不用预订接机，出机场直接打滴走更快更方便。&lt;/p&gt;
&lt;h2&gt;日程&lt;/h2&gt;
&lt;h4&gt;Day 1&lt;/h4&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202406051731336.png&quot; alt=&quot;Pasted Graphic&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;Pasted Graphic&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;来泰国的第一站安排给了火遍小红书的、网传各大明星都纷纷去祭拜的「四面佛」，但现场的实际情况有点失望，是在路边的很小的一块区域，紧挨十字路口，旁边就是车来车往的马路，跟其他去过的宗教场所一样，现场可以购买各种鲜花、大象雕塑等物品供奉给佛像表达敬意并换取保佑，不过在祭拜的当地人确实很多，倒是游客一般只是参观，总的来说不推荐。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503162348772-20250714164619860.jpeg&quot; alt=&quot;img&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;img&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;「卧佛寺」、「大皇宫」、「郑王庙」三个建筑群大同小异，就是一种国内没有的宗教建筑风格，选一个仔细参观即可，三个地方全部逛完的话会审美疲劳。但是夜晚的「郑王庙」一定不能错过，一旦夜晚降临，亮灯的「郑王庙」一定是湄南河畔附近最美的景色，观赏的地点可以选在湄南河的游轮上，或者河畔对面的咖啡厅（推荐 vivi the coffee place），金色的寺庙孤立在湄南河畔，道道金光洒在在湄南河水上金光粼粼，绝美。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242327459.jpeg&quot; alt=&quot;6A73D29A-C1BA-495C-B271-0533F6233EB1_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;6A73D29A-C1BA-495C-B271-0533F6233EB1_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242328817.jpeg&quot; alt=&quot;4C6681D2-078A-48FA-9986-F5BB7AE3762B_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;4C6681D2-078A-48FA-9986-F5BB7AE3762B_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;「死亡博物馆」前几天行程里我很喜欢的一站，Siriraj医学院对外开放的医学博物馆，整体参观完的心情是沉重的，死于连环杀手下的受害者尸体、癌症恶化的器官标本、绝症患者的横切面、泡在防腐缸的畸形婴儿、甚至可以清晰的看到缸中婴儿的睫毛。走出博物馆的大门，曼谷40度的空气扑面而来，感恩自己还可以呼吸。&lt;/p&gt;
&lt;p&gt;「考山路夜市」给我感觉只有“乱”，感觉整条路的商家都在比拼谁的音响更胜一筹，隔几条街都可以听见的混乱音乐、弥漫在空气中的大麻味道、二手烟、混合欧美白男的香水和汗臭味，勉强屏住呼吸才可以在街上穿梭，饭菜的辣椒油中也散发出熏鼻的汗臭味，分不清是商家特意调制，还是长期放置吸收了太多空气中的汗液，不理解。&lt;/p&gt;
&lt;h4&gt;Day 2&lt;/h4&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202406071632365.png&quot; alt=&quot;Pasted Graphic 1&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;Pasted Graphic 1&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;「Bangkok网红天桥」已经刷爆国内小红书了，曼谷新地标之一，拍照打卡的中国人很多，井然有序的排队打卡，将近40度的温度下摆两分钟姿势就满头大汗。从天桥下来就是「曼谷文化艺术中心」，在曼谷算是最大的艺术展览中心了，但是放国内的话个人感觉比较一般了，不多赘述，路过可以去，没必要刻意跑一趟。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503170007011.jpeg&quot; alt=&quot;6E65B424-751C-4C7B-AB56-CDEC3523C7FF_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;6E65B424-751C-4C7B-AB56-CDEC3523C7FF_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;「水门寺大佛」也是在简中网上很火，但如果满怀期待的去的话，恐怕不会很满意，就是一座城区中的大佛，网上的照片一般都是用超长焦拍出来的，所以显得主体会比较大，真实情况可以参考下面我用105焦距镜头拍摄的效果，可以在 796soi thoet thai 26、60 Phet Kasem 15 Alley 这个位置拍摄，另外周围会有一条河，有游船参观的服务，介意河水的颜色和味道，个人建议还是不要尝试。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242331900.jpeg&quot; alt=&quot;1C24D257-1C43-4505-AA4F-3DA9F0E0DFD7_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1C24D257-1C43-4505-AA4F-3DA9F0E0DFD7_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242331523.jpeg&quot; alt=&quot;8D3D2528-EA43-477E-8C1E-C8D8FD3CE471_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;8D3D2528-EA43-477E-8C1E-C8D8FD3CE471_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;「Iconsiam」就在湄南河沿岸，坐公交船就可以直达，算是目前曼谷最新的商场了，门店种类和客流量都是曼谷屈指可数，一楼是室内集市，有曼谷各种街头小吃的干净版本，可以放心吃，楼上还有很多家米其林餐厅，唯一可惜的是没有比较好的面馆。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242332051.jpeg&quot; alt=&quot;48C2B81E-3DC5-43D3-B464-9E16B32B500B_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;48C2B81E-3DC5-43D3-B464-9E16B32B500B_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242332202.jpeg&quot; alt=&quot;3001FD2D-6F22-455F-A536-8DE157A75F67_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;3001FD2D-6F22-455F-A536-8DE157A75F67_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h4&gt;Day 3、Day4&lt;/h4&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202408122310523.png&quot; alt=&quot;image-20240812231017462&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;image-20240812231017462&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;「金东尼人妖秀」是大众尺度里最出名的，但并不推荐，节目类似联欢晚会，音乐和舞蹈的结合，都是假唱，观赏体验一般，演出结束会有合影阶段，给人妖小费就可以合影，人妖都会很热情。顺便推荐下饼叔的视频 &lt;a href=&quot;https://www.bilibili.com/video/BV19E4m1R761/?share_source=copy_web&amp;amp;vd_source=077a852cf5a4cf09748549d95a01026b&quot;&gt;「神 鬼 传 奇」&lt;/a&gt; ，里面有一段对人妖表演者的采访，可以帮助你更好的理解这种职业。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242333755.jpeg&quot; alt=&quot;7D49D6C8-EDA9-4365-AC32-0014CD3C5052_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;7D49D6C8-EDA9-4365-AC32-0014CD3C5052_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242333215.jpeg&quot; alt=&quot;3870acfe181e9a107c3318b54cd605d5&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;3870acfe181e9a107c3318b54cd605d5&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;「拉差达慕拳击馆」充满荷尔蒙的泰拳比赛🥊，算是个人感觉来泰国最喜欢的一个环节，现场氛围满满，价格1600-2500🐷不等，建议一等座，体验感拉满。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503162350580.jpeg&quot; alt=&quot;2D9BB3ED-4084-4AB8-91E9-22ED67458E06_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;2D9BB3ED-4084-4AB8-91E9-22ED67458E06_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;「Moon bar」花小钱装大杯，曼谷悦榕庄酒店59楼的一家清吧，有酒水就餐食，门票可以抵扣消费，可以拍拍照打卡，很出片。同类网上比较火的还有「像素大厦」但是后者排队太严重了。&lt;/p&gt;
&lt;h4&gt;Day 5、Day 6&lt;/h4&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503151842302.png&quot; alt=&quot;image-20250315184233283&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;image-20250315184233283&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;「真理寺」一座用不完工的木质宫殿，里面所有建筑都是由木头建造，常年在海边受侵蚀，所以一直在修筑，还是很壮观的，还会有中文讲解，会讲解各个所雕刻神佛的含义，不过个人对这种神神怪怪的不感冒。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503162358493.jpeg&quot; alt=&quot;8CA27EAF-0FD7-4D95-952C-B5A98640A845_1_102_a&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;8CA27EAF-0FD7-4D95-952C-B5A98640A845_1_102_a&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;「大象村」好玩！推荐！可以骑大象，有水路有陆路，会载着你围着景区转一圈。门票是直接包含骑大象30min + 椰子🥥 + 表演，为数不多及其推荐的景点。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242334375.jpeg&quot; alt=&quot;34983618-E950-4468-8BD3-D54C044947D5_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;34983618-E950-4468-8BD3-D54C044947D5_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503242334288.jpeg&quot; alt=&quot;7EAC5F4E-BCE5-4042-B826-ABB726F56946_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;7EAC5F4E-BCE5-4042-B826-ABB726F56946_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;「79 SHOW」只能用炸裂一个字来形容，入场会收手机、相机等拍摄设备，场内昏暗，灯光只聚集的舞台上，座位是随便做的，先到先得，中途有人走了可以去换座位，前三排是互动区，互动很炸裂，不想互动不要坐前排可以是来芭提雅必打卡的项目。&lt;/p&gt;
&lt;p&gt;「中天海滩夜市」与中天海滩只隔了一条马路，下午5点开始营业，买好没事喝啤酒可以坐在路边木凳上欣赏日落，还可以到对面的中天海滩散步，很惬意。&lt;/p&gt;
&lt;h4&gt;Day 7&lt;/h4&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503151843481.png&quot; alt=&quot;image-20250315184301457&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;image-20250315184301457&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;「格兰岛」在网络上有三个比较火的地方，Tawaen、Tien Beach、Samae Beach，我们去的是Tawean，游玩项目是最丰富的，拖拽伞、玻璃船、摩托艇、香蕉船、浮潜砍完价一共1500🐷左右，一定要砍价，会优惠不少。岛上可以租突突车自己转着玩，不过山路比较崎岖，要注意安全。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202503170000996.jpeg&quot; alt=&quot;25780240-A7F6-461D-9B60-D06ADB589BD2_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;25780240-A7F6-461D-9B60-D06ADB589BD2_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;</content:encoded></item><item><title>关于 IDEA Plugin 开发</title><link>https://folay.top/zh/blog/idea_plugin</link><guid isPermaLink="true">https://folay.top/zh/blog/idea_plugin</guid><description>IDEA插件开发经验分享，包括PSI编程结构接口、代码格式化实现和静态代码检查功能的设计与实现。</description><pubDate>Sat, 11 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;介于团队的代码格式化插件过于远古，我对其进行了升级重写。新的实现切换了代码格式化的核心库，增加了对 Kotlin 语言的支持，并新增了若干 Inspections 静态代码检查功能。在这里记录此过程中，我对 IDEA Plugin 的理解以及首次进行相关开发的经验。&lt;/p&gt;
&lt;h2&gt;为什么需要 IDEA Plugin？&lt;/h2&gt;
&lt;p&gt;聚焦于 Android 开发场景，我理解 IDEA Plugin 开发的必要性主要体现在两点：&lt;strong&gt;统一规范&lt;/strong&gt; 与 &lt;strong&gt;开发提效&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;IDE 作为开发工具，是部署在开发者本地的，团队的每个成员都有自定义其配置的能力。如果不同开发者使用不同的开发规范和代码规范，势必会导致代码管理上的混乱。没有人希望在拉取远程最新代码时，因为代码格式不统一而弹出大量的冲突问题。&lt;/p&gt;
&lt;p&gt;除了代码格式上的规范，开发团队对于代码安全等方面也有不同的规范。例如，对 &lt;code&gt;java.util.Random&lt;/code&gt; 伪随机数的使用，以及 &lt;code&gt;Color.parseColor()&lt;/code&gt; 方法在解析未知内容变量时可能引发的 &lt;code&gt;IllegalArgumentException&lt;/code&gt; 异常。如果这些问题在开发阶段没有被拦截，工作量就会堆积到 Code Review 阶段，甚至可能被带到线上。这些琐碎问题的堆积，会为项目代码质量和安全性制造隐患，从而影响日常项目的开发效率。&lt;/p&gt;
&lt;p&gt;所有这些问题都可以通过定制 IDEA Plugin 来解决。总而言之，IDEA Plugin 开发的目的就是满足团队在代码规范、架构约束以及独特工作流上的个性化需求，解决 &lt;strong&gt;特定团队&lt;/strong&gt; 的 &lt;strong&gt;特定痛点&lt;/strong&gt;，以此提高团队的工程效率。&lt;/p&gt;
&lt;h2&gt;PSI&lt;/h2&gt;
&lt;p&gt;**PSI (Program Structure Interface) **是 IntelliJ 平台中一个核心的概念，它是对代码的一种抽象表示。可以将其类比为 Android 视图系统中的 &lt;code&gt;View&lt;/code&gt; 层次结构。在 Android 中，UI 界面的所有元素都被抽象为 &lt;code&gt;View&lt;/code&gt; 对象，并以树状结构组织起来，我们可以通过遍历 &lt;code&gt;View&lt;/code&gt; 树来查找、修改或操作 UI 元素。同样，PSI 将源代码（如 Java、Kotlin 文件）解析成一个抽象语法树（AST），树中的每个节点都是一个 &lt;code&gt;PsiElement&lt;/code&gt;，代表了代码中的一个结构，例如类、方法、变量、表达式等。&lt;/p&gt;
&lt;p&gt;具体来说，&lt;code&gt;PsiElement&lt;/code&gt; 是 PSI 层次结构的基类，所有表示代码元素的类都继承自它。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PsiFile&lt;/code&gt;：代表一个源代码文件，是 PSI 树的根节点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PsiClass&lt;/code&gt;：代表一个类或接口。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PsiMethod&lt;/code&gt;：代表一个方法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PsiExpression&lt;/code&gt;：代表一个表达式，比如 &lt;code&gt;new Random()&lt;/code&gt; 或者 &lt;code&gt;Color.parseColor(&quot;#FFFFFF&quot;)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PsiReferenceExpression&lt;/code&gt;：代表一个引用表达式，例如方法调用中的方法名部分。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PsiNewExpression&lt;/code&gt;：代表一个 &lt;code&gt;new&lt;/code&gt; 关键字创建对象的表达式。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PsiMethodCallExpression&lt;/code&gt;：代表一个方法调用表达式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用 PSI，可以在不直接操作文本内容的情况下，以结构化的方式访问、分析和修改代码。这就像在 Android 中，我们通过 &lt;code&gt;findViewById&lt;/code&gt; 找到一个 &lt;code&gt;Button&lt;/code&gt; 对象，然后调用它的 &lt;code&gt;setText()&lt;/code&gt; 方法来改变按钮的文本，而不是直接修改布局文件中的 XML 字符串。&lt;/p&gt;
&lt;p&gt;在 IDEA Plugin 开发中，通常会用到 &lt;code&gt;PsiDocumentManager&lt;/code&gt; 来获取当前文档对应的 &lt;code&gt;PsiFile&lt;/code&gt;。例如，在文件保存前对文档进行操作时，通过 &lt;code&gt;PsiDocumentManager.getInstance(project).getPsiFile(document)&lt;/code&gt; 可以获取到与 &lt;code&gt;Document&lt;/code&gt; 关联的 &lt;code&gt;PsiFile&lt;/code&gt; 对象。有了 &lt;code&gt;PsiFile&lt;/code&gt;，我们就可以利用 PSI 提供的各种 API 来遍历和分析代码结构，进而实现代码检查、重构、格式化等功能。&lt;/p&gt;
&lt;h2&gt;Format&lt;/h2&gt;
&lt;p&gt;之前是插件内部维护格式化规则，在升级后我将具体的格式化规则交给成熟的开源库，Java 使用 &lt;code&gt;google-java-format&lt;/code&gt;、Kotlin 使用 &lt;code&gt;ktfmt&lt;/code&gt;，并重写格式化逻辑，尽可能的精简代码，提高维护性。大体流程可以分为：注册、监听、格式化。&lt;/p&gt;
&lt;p&gt;插件启动时，通过 &lt;code&gt;FormatInstaller&lt;/code&gt; 将自定义的 &lt;code&gt;FormatCodeStyleManager&lt;/code&gt; &lt;strong&gt;注册&lt;/strong&gt;为项目的 &lt;code&gt;CodeStyleManager&lt;/code&gt; 。它会包装IntelliJ IDEA原生的 &lt;code&gt;CodeStyleManager&lt;/code&gt; ，并根据文件类型决定是使用自定义格式化逻辑还是委托给原生管理器。&lt;/p&gt;
&lt;p&gt;通过实现 &lt;code&gt;DocumentManagerListener&lt;/code&gt;，插件可以 &lt;strong&gt;监听&lt;/strong&gt; 文件保存前的事件。当用户保存 Java 或 Kotlin 文件时，&lt;code&gt;DocumentManagerListener&lt;/code&gt; 的 &lt;code&gt;beforeDocumentSaving&lt;/code&gt;方法会被回调，并触发格式化。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DocumentManagerListener&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FileDocumentManagerListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;beforeDocumentSaving&lt;/span&gt;&lt;span&gt;(@&lt;/span&gt;&lt;span&gt;NotNull&lt;/span&gt;&lt;span&gt; Document &lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// ... 获取当前 Project 和 PsiFile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (psiFile &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; (psiFile.&lt;/span&gt;&lt;span&gt;getName&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;endsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.java&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; psiFile.&lt;/span&gt;&lt;span&gt;getName&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;endsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.kt&quot;&lt;/span&gt;&lt;span&gt;))) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ApplicationManager.&lt;/span&gt;&lt;span&gt;getApplication&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;invokeLater&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; WriteCommandAction.&lt;/span&gt;&lt;span&gt;Simple&lt;/span&gt;&lt;span&gt;(project) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;protected&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;// 调用 CodeStyleManagerDecorator 进行格式化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CodeStyleManagerDecorator.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;(project).&lt;/span&gt;&lt;span&gt;reformatText&lt;/span&gt;&lt;span&gt;(psiFile, Collections.&lt;/span&gt;&lt;span&gt;singletonList&lt;/span&gt;&lt;span&gt;(psiFile.&lt;/span&gt;&lt;span&gt;getTextRange&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;// 保存文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ApplicationManager.&lt;/span&gt;&lt;span&gt;getApplication&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;invokeLater&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; FileDocumentManager.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;saveDocument&lt;/span&gt;&lt;span&gt;(document));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;FormatCodeStyleManager &lt;/code&gt;接收到格式化请求后，会根据文件类型（Java 或 Kotlin）调用相应的格式化工具，获取对应的格式化结果。最终，所有替换操作在一个 &lt;code&gt;WriteCommandAction&lt;/code&gt; 中执行，确保原子性。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;formatInternal&lt;/span&gt;&lt;span&gt;(PsiFile file, Collection&lt;/span&gt;&lt;span&gt;&amp;lt;?&lt;/span&gt;&lt;span&gt; extends TextRange&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; ranges) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (JavaFileType.INSTANCE.&lt;/span&gt;&lt;span&gt;equals&lt;/span&gt;&lt;span&gt;(file.&lt;/span&gt;&lt;span&gt;getFileType&lt;/span&gt;&lt;span&gt;())) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;performReplacements&lt;/span&gt;&lt;span&gt;(document, JavaFormatterUtil.&lt;/span&gt;&lt;span&gt;getReplacements&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Formatter&lt;/span&gt;&lt;span&gt;(), document.&lt;/span&gt;&lt;span&gt;getText&lt;/span&gt;&lt;span&gt;(), ranges));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (KotlinFileType.INSTANCE.&lt;/span&gt;&lt;span&gt;getName&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;equals&lt;/span&gt;&lt;span&gt;(file.&lt;/span&gt;&lt;span&gt;getFileType&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;getName&lt;/span&gt;&lt;span&gt;())) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;performReplacements&lt;/span&gt;&lt;span&gt;(document, KotlinFormatterUtil.&lt;/span&gt;&lt;span&gt;getReplacements&lt;/span&gt;&lt;span&gt;(KotlinUiFormatterStyle.GOOGLE, document.&lt;/span&gt;&lt;span&gt;getText&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;performReplacements&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; Document document, &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; Map&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;TextRange, String&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; replacements) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (replacements.&lt;/span&gt;&lt;span&gt;isEmpty&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TreeMap&amp;lt;&lt;/span&gt;&lt;span&gt;TextRange&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&amp;gt; sorted &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; TreeMap&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span&gt;comparing&lt;/span&gt;&lt;span&gt;(TextRange&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;getStartOffset));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sorted.&lt;/span&gt;&lt;span&gt;putAll&lt;/span&gt;&lt;span&gt;(replacements);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;WriteCommandAction.&lt;/span&gt;&lt;span&gt;runWriteCommandAction&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;getProject&lt;/span&gt;&lt;span&gt;(), () &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (Entry&amp;lt;&lt;/span&gt;&lt;span&gt;TextRange&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&amp;gt; entry &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; sorted.&lt;/span&gt;&lt;span&gt;descendingMap&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;entrySet&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;document.&lt;/span&gt;&lt;span&gt;replaceString&lt;/span&gt;&lt;span&gt;(entry.&lt;/span&gt;&lt;span&gt;getKey&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;getStartOffset&lt;/span&gt;&lt;span&gt;(), entry.&lt;/span&gt;&lt;span&gt;getKey&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;getEndOffset&lt;/span&gt;&lt;span&gt;(), entry.&lt;/span&gt;&lt;span&gt;getValue&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PsiDocumentManager.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;getProject&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;commitDocument&lt;/span&gt;&lt;span&gt;(document);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;Inspection&lt;/h2&gt;
&lt;p&gt;还在插件中添加了一系列静态代码检查功能（Inspections），目的是帮助团队成员在开发阶段发现并修正代码中潜在的问题。这与 Android 开发中 Lint 工具的目标非常相似，都是在编译或开发阶段提供反馈，避免问题流入后期。&lt;/p&gt;
&lt;p&gt;一个 Inspection 的实现通常继承自 &lt;code&gt;AbstractBaseJavaLocalInspectionTool&lt;/code&gt;（或其对应语言的基类），并重写 &lt;code&gt;buildVisitor&lt;/code&gt; 方法，返回一个 &lt;code&gt;PsiElementVisitor&lt;/code&gt;。这个 &lt;code&gt;Visitor&lt;/code&gt; 会遍历 PSI 树，在访问特定类型的 &lt;code&gt;PsiElement&lt;/code&gt; 时执行检查逻辑。如果发现问题，就通过 &lt;code&gt;ProblemsHolder.registerProblem&lt;/code&gt; 注册一个 &lt;code&gt;ProblemDescriptor&lt;/code&gt;，这会在 IDE 中以高亮的形式提示开发者，并可以附带一个 &lt;code&gt;LocalQuickFix&lt;/code&gt; 来提供自动修复功能。&lt;/p&gt;
&lt;p&gt;介于大多数 Inspections 都是强业务相关的，这里只挑选两个与业务逻辑无关的纯技术规范侧的检查项。&lt;/p&gt;
&lt;h3&gt;Random 伪随机数&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;目的&lt;/strong&gt;：检测代码中是否使用了 &lt;code&gt;java.util.Random&lt;/code&gt; 这个伪随机数生成器。在安全性要求较高的场景，推荐使用 &lt;code&gt;java.security.SecureRandom&lt;/code&gt; 以获取更好的随机性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现&lt;/strong&gt;：通过重写 &lt;code&gt;JavaElementVisitor&lt;/code&gt; 的 &lt;code&gt;visitNewExpression&lt;/code&gt; 方法，检查 &lt;code&gt;PsiNewExpression&lt;/code&gt; 中创建的对象是否为 &lt;code&gt;java.util.Random&lt;/code&gt; 的实例。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;快速修复 (&lt;code&gt;RandomQuickFix&lt;/code&gt;)&lt;/strong&gt;：如果检测到问题，会提供一个 Quick Fix，将 &lt;code&gt;new java.util.Random()&lt;/code&gt; 替换为 &lt;code&gt;new java.security.SecureRandom()&lt;/code&gt;，并自动导入 &lt;code&gt;SecureRandom&lt;/code&gt; 类。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visitNewExpression&lt;/span&gt;&lt;span&gt;(PsiNewExpression expression) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;visitNewExpression&lt;/span&gt;&lt;span&gt;(expression);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;String qualifiedName &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PsiJavaCodeReferenceElement classReference &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; expression.&lt;/span&gt;&lt;span&gt;getClassReference&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (classReference &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;qualifiedName &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; classReference.&lt;/span&gt;&lt;span&gt;getQualifiedName&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&quot;java.util.Random&quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;equals&lt;/span&gt;&lt;span&gt;(qualifiedName)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;holder.&lt;/span&gt;&lt;span&gt;registerProblem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;expression, INSPECTION_DESCRIPTION, INSPECTION_TYPE, &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RandomQuickFix&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RandomQuickFix&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LocalQuickFix&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;applyFix&lt;/span&gt;&lt;span&gt;(@&lt;/span&gt;&lt;span&gt;NotNull&lt;/span&gt;&lt;span&gt; Project &lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;, @&lt;/span&gt;&lt;span&gt;NotNull&lt;/span&gt;&lt;span&gt; ProblemDescriptor &lt;/span&gt;&lt;span&gt;descriptor&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PsiElementFactory factory &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; JavaPsiFacade.&lt;/span&gt;&lt;span&gt;getElementFactory&lt;/span&gt;&lt;span&gt;(project);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PsiExpression newExpression &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;factory.&lt;/span&gt;&lt;span&gt;createExpressionFromText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;new java.security.SecureRandom()&quot;&lt;/span&gt;&lt;span&gt;, descriptor.&lt;/span&gt;&lt;span&gt;getPsiElement&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PsiElement replacedElement &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; descriptor.&lt;/span&gt;&lt;span&gt;getPsiElement&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(newExpression);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;JavaCodeStyleManager codeStyleManager &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; JavaCodeStyleManager.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;(project);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;codeStyleManager.&lt;/span&gt;&lt;span&gt;shortenClassReferences&lt;/span&gt;&lt;span&gt;(replacedElement);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;codeStyleManager.&lt;/span&gt;&lt;span&gt;optimizeImports&lt;/span&gt;&lt;span&gt;(replacedElement.&lt;/span&gt;&lt;span&gt;getContainingFile&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3&gt;Color.parseColor()&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;目的&lt;/strong&gt;：当 &lt;code&gt;Color.parseColor()&lt;/code&gt; 方法解析的是一个未知内容的变量时，建议使用 &lt;code&gt;try-catch&lt;/code&gt; 块捕获 &lt;code&gt;IllegalArgumentException&lt;/code&gt; 异常，以增强代码的健壮性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现&lt;/strong&gt;：通过重写 &lt;code&gt;JavaElementVisitor&lt;/code&gt; 的 &lt;code&gt;visitMethodCallExpression&lt;/code&gt; 方法，检查方法调用是否为 &lt;code&gt;Color.parseColor&lt;/code&gt;。如果方法的参数不是常量或字面量，并且当前调用没有被 &lt;code&gt;try-catch&lt;/code&gt; 块包裹，则注册问题。&lt;code&gt;isWrappedInTryCatch&lt;/code&gt; 方法会向上遍历 PSI 树，查找是否存在 &lt;code&gt;PsiTryStatement&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;快速修复 (&lt;code&gt;ColorParseFix&lt;/code&gt;)&lt;/strong&gt;：提供 Quick Fix，将 &lt;code&gt;Color.parseColor()&lt;/code&gt; 的调用语句用一个 &lt;code&gt;try-catch&lt;/code&gt; 块包裹起来。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 从 ColorParseInspection 中精简&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visitMethodCallExpression&lt;/span&gt;&lt;span&gt;(PsiMethodCallExpression expression) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;visitMethodCallExpression&lt;/span&gt;&lt;span&gt;(expression);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// ... 获取 methodExpression&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&quot;Color.parseColor&quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;equals&lt;/span&gt;&lt;span&gt;(method)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PsiExpression argument &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; argumentList.&lt;/span&gt;&lt;span&gt;getExpressions&lt;/span&gt;&lt;span&gt;()[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;PsiUtil.&lt;/span&gt;&lt;span&gt;isConstantExpression&lt;/span&gt;&lt;span&gt;(argument)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;(argument &lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; PsiLiteralExpression)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;isWrappedInTryCatch&lt;/span&gt;&lt;span&gt;(expression)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;holder.&lt;/span&gt;&lt;span&gt;registerProblem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;expression, INSPECTION_DESCRIPTION, INSPECTION_TYPE, &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ColorParseFix&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isWrappedInTryCatch&lt;/span&gt;&lt;span&gt;(PsiElement element) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// ... 向上遍历 PSI 树查找 PsiTryStatement&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ColorParseFix&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;implements&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LocalQuickFix&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;applyFix&lt;/span&gt;&lt;span&gt;(@&lt;/span&gt;&lt;span&gt;NotNull&lt;/span&gt;&lt;span&gt; Project &lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;, @&lt;/span&gt;&lt;span&gt;NotNull&lt;/span&gt;&lt;span&gt; ProblemDescriptor &lt;/span&gt;&lt;span&gt;problemDescriptor&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// ... 创建 try-catch 代码块并替换原始语句&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;AS 适配&lt;/h2&gt;
&lt;p&gt;Android Studio 基于 IntelliJ IDEA 平台构建。为了允许插件访问 JDK 内部的 API（这些 API 可能是 &lt;code&gt;google-java-format&lt;/code&gt; 等库所依赖的），需要在JVM启动参数中添加特定的 &lt;code&gt;--add-opens&lt;/code&gt; 和 &lt;code&gt;--add-exports&lt;/code&gt; 选项。如果这些 JVM 选项不足，可能会导致插件运行时出现 &lt;code&gt;InaccessibleObjectException&lt;/code&gt; 等错误，从而使代码格式化功能失效。不同版本的 IDEA 平台设置这些 JVM 参数的方式有所不同。&lt;/p&gt;
&lt;p&gt;如果基线版本大于等于 213，直接调用 &lt;code&gt;VMOptions.setOption()&lt;/code&gt; 来设置所需的 &lt;code&gt;--add-opens&lt;/code&gt; 和 &lt;code&gt;--add-exports&lt;/code&gt; 参数。&lt;/p&gt;
&lt;p&gt;如果基线版本低于 213，则需要手动修改 VM Options 文件。它会获取 VM Options 文件的路径，读取其内容，如果文件中不包含所需的参数，则将这些参数追加到文件末尾，并确保文件可写。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (version &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;213&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;VMOptions.&lt;/span&gt;&lt;span&gt;setOption&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;--add-opens=java.base/java.lang&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;=ALL-UNNAMED&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;VMOptions.&lt;/span&gt;&lt;span&gt;setOption&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;--add-opens=java.base/java.util&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;=ALL-UNNAMED&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Path path &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VMOptions.&lt;/span&gt;&lt;span&gt;getWriteFile&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (path &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;File file &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;toFile&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;String content &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileUtil.&lt;/span&gt;&lt;span&gt;loadFile&lt;/span&gt;&lt;span&gt;(file);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;content.&lt;/span&gt;&lt;span&gt;contains&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;--add-opens=java.base/java.lang=ALL-UNNAMED&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;content &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; content &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;&quot;--add-opens=java.base/java.lang=ALL-UNNAMED&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;&quot;--add-opens=java.base/java.util=ALL-UNNAMED&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;FileUtil.&lt;/span&gt;&lt;span&gt;writeToFile&lt;/span&gt;&lt;span&gt;(file, content);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (IOException &lt;/span&gt;&lt;span&gt;ignored&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;</content:encoded></item><item><title>稳定的理想主义者</title><link>https://folay.top/zh/blog/infj</link><guid isPermaLink="true">https://folay.top/zh/blog/infj</guid><description>INFJ人格类型的自我分析，对比16personalities网站描述与个人真实情况的差异，探讨MBTI测试的准确性。</description><pubDate>Sat, 03 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;事情是这样的，在最近一坤年的跨度里我做过 5 次 MBTI 人格类型分析，最终得出的结果都是 「INFJ-A」，期间还经历了题库的几次迭代变化，所以能如此稳定的得到统一结果，这让我还是蛮意外的。为了进一步验证 MBTI 对于人格类型归纳的准确性，我打算将网上对于 INFJ 人格的描述与自己察觉的真实情况进行比较。&lt;/p&gt;
&lt;p&gt;
  下文中引用的内容均来自于
  &lt;a href=&quot;https://www.16personalities.com&quot; target=&quot;_blank&quot;&gt;
    16personalities
  &lt;/a&gt;
  网站中对于 INFJ 人格的描述。
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;INFJ 可能是最稀有的人格类型，但他们肯定会在世界上留下自己的印记。他们理想主义、有原则，不满足于平平安安地度过一生—他们想要站起来，有所作为。对于 INFJ 来说，成功不是来自金钱或地位，而是来自寻求成就感，帮助他人，成为世界上一股向善的力量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最稀有的人格类型？哈哈查了一些资料发现，国内互联网上关于 MBTI 的内容大多都是直接 “ 英译汉 ” 从外网搬运来的，所以引用中的修饰词也要加上 “ America ” 的前缀，实际上在国内的特殊环境下成长的 95 后中 INFJ 人格类型的占比还是不少的，国内最稀少的人格类型应该是被称为「指挥官」的 ENTJ。当然这些不属于自我人格的范畴。&lt;/p&gt;
&lt;p&gt;回到正题本身，“ 理想主义、原则性 ” 这些标签我是欣然接受的，毕竟寻找理论落地过程中由于人类劣根性导致的偏差是我乐子之一。但 “ 不满足于平淡生活 ” 这就有点不符合了，近几年所作的事情和近期对未来的规划都是在为畅想中的 “ 平淡生活 ” 作准备，虽然我很纠结于 “ 人生意义 ” 这个话题，但这种意义不是来自于社会地位的提升、也不是外在的物质财富，而是来自于内心畅想的实现，或者说将自己所坚持的规则（原则）具现出来并应用到现实社会。所以…哈哈太难了，畅想仅仅是畅想。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;INFJ 往往会有一种与大多数人不同的感觉，由于他们丰富的内心生活和寻找生活目标的深沉而持久的渴望，他们并不总是与周围的人融为一体，幸运的是，这种不合拍的感觉并没有削弱 INFJ 让世界变得更美好的承诺。&lt;/p&gt;
&lt;p&gt;INFJ 受到不公正的困扰，他们通常更关心利他主义而不是个人利益，许多 INFJ 将帮助他人视为生活中的使命，他们一直在寻找介入并为正确的事情大声疾呼的方法。具有这种人格类型的人也渴望解决社会更深层次的问题，希望不公平和艰辛能够成为过去。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;别说了，像在被讽刺一样，如果我从小在一个比较包容开放，接受多元价值观的地方成长，那可能确实会像上面说的一样现在还在坚持倡导一些事情。但事实是我目前想不到任何我所能做的可以 “ 让世界变得更美好 ” 的事情，只能顾好自己和珍视的人，虽然现在看见 “ 精致的利己主义者 ” 还会恶心作呕，但也不会有任何想要争辩或者 “ 他是错的我是对的 ” 这样的想法。其实我也不确认对方是错的，只是不喜欢而已。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;INFJ 可能内向，但他们重视与他人的深厚、真实的关系。很少有事情会像真正认识另一个人一样给这些性格类型带来快乐—同时也为别人所理解。INFJ 喜欢有意义的对话远远超过闲聊，他们倾向于以热情和敏感的方式进行交流。这种情感上的诚实和洞察力可以给周围的人留下深刻的印象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这倒是真的，我虽然是个典型的 i 人，但对于认识新朋友，特别是了解新朋友、与新朋友进行深度对话，我是毫不吝啬的，在解锁一个人的人生经历、情感经历、特别是三观塑造历程的时，我内心得到的满足和快感是十分强烈的。（我始终于好奇不同成长经历对于个体三观塑造的影响）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;INFJ 喜欢在工作中保持整洁有序，喜欢平和、安静的气氛，希望工作中每个人的贡献能得到肯定，每个人都有成就感，所有的工作都能和谐地取得良好的结果。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这让我不得不贴一下我使用了两年的工位了，就像我 About 页所写的一样，「有序」始终是我生活的主旋律。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/202402061752931.png&quot; width=&quot;100%/&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;最后，其实之前在纠结 MBTI 准确性的时，有跟朋友讨论过 MBTI 与星座的区别，讨论过程有个类比觉得十分恰当。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一场考试中你物理得分 90 分，化学得分 60 分，说明你物理比较好，我会给你打上 “ 物理 ” 的标签，这就是 MBTI，MBTI 中对于人格的描述与自我察觉的实际情况间的差异可以看作 “ 考试中影响结果的运气等不稳定因素 ”。而星座的话，就是不用参加考试，直接根据你的生辰来判定你物理、化学所得的分数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;就这样吧，希望有个愉快的春节。&lt;/p&gt;</content:encoded></item><item><title>2023 年国庆自驾川藏线摄影记录</title><link>https://folay.top/zh/blog/318</link><guid isPermaLink="true">https://folay.top/zh/blog/318</guid><description>2023年国庆期间自驾川藏线的摄影记录，记录了从成都到西藏沿途的美丽风景和难忘经历。</description><pubDate>Thu, 19 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;成都太古里裸眼 3D 巨幕，很震撼。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/8794695C-73F1-4B96-946A-58012A58A255_1_105_c.jpeg&quot; alt=&quot;8794695C-73F1-4B96-946A-58012A58A255_1_105_c&quot; loading=&quot;eager&quot; /&gt;&lt;figcaption&gt;8794695C-73F1-4B96-946A-58012A58A255_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;

&lt;p&gt;雅安自驾大本营回身随拍，318 旅途的起点。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/7AE60657-9E28-48B3-BB36-C9384EA6D12D_1_105_c.jpeg&quot; alt=&quot;7AE60657-9E28-48B3-BB36-C9384EA6D12D_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;7AE60657-9E28-48B3-BB36-C9384EA6D12D_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;驾离市区，向大山前进，海拔开始拔升，云气触手可摸。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/26229F08-9B05-4941-B9EA-8E48214717CD_1_105_c.jpeg&quot; alt=&quot;26229F08-9B05-4941-B9EA-8E48214717CD_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;26229F08-9B05-4941-B9EA-8E48214717CD_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;路上随处可见的经幡和国旗。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/950CB7B5-5767-48E4-AA52-8261AAD0CFEE_1_105_c.jpeg&quot; alt=&quot;950CB7B5-5767-48E4-AA52-8261AAD0CFEE_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;950CB7B5-5767-48E4-AA52-8261AAD0CFEE_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;云雾弥漫中的折多山。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/4C481D9B-53F4-4C37-B353-59F102DD31DE_1_105_c.jpeg&quot; alt=&quot;4C481D9B-53F4-4C37-B353-59F102DD31DE_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;4C481D9B-53F4-4C37-B353-59F102DD31DE_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;团队的第一张合照。（我是摄影狮&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/E13F7074-A0E9-44F4-BB2B-3B1B5B09F681_1_105_c.jpeg&quot; alt=&quot;E13F7074-A0E9-44F4-BB2B-3B1B5B09F681_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;E13F7074-A0E9-44F4-BB2B-3B1B5B09F681_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;我在折多山很想你。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/C25FA65B-80DC-44E2-B023-9810D0B4651F_1_105_c.jpeg&quot; alt=&quot;C25FA65B-80DC-44E2-B023-9810D0B4651F_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;C25FA65B-80DC-44E2-B023-9810D0B4651F_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;喜马拉雅山脉阻断的印度洋暖湿气流形成的云雾。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/EDA0719D-6580-402B-B137-FC3997CFADF8_1_105_c.jpeg&quot; alt=&quot;EDA0719D-6580-402B-B137-FC3997CFADF8_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;EDA0719D-6580-402B-B137-FC3997CFADF8_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;新都桥民宿的藏族老板招待的牦牛肉火锅，超香！（有个伙计因为高反在休息，错过了&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/9135CBBB-66AF-4C95-8D59-9CD1C15F9829_1_105_c.jpeg&quot; alt=&quot;9135CBBB-66AF-4C95-8D59-9CD1C15F9829_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;9135CBBB-66AF-4C95-8D59-9CD1C15F9829_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;民宿门口的无名小河。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/DB42D55D-9CE2-44F4-9987-B41EAC729261_1_105_c.jpeg&quot; alt=&quot;DB42D55D-9CE2-44F4-9987-B41EAC729261_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;DB42D55D-9CE2-44F4-9987-B41EAC729261_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;继续出发，行驶在山野。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/71FD1F73-CB3E-4D98-9D01-5171CDAEA7D0_1_105_c.jpeg&quot; alt=&quot;71FD1F73-CB3E-4D98-9D01-5171CDAEA7D0_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;71FD1F73-CB3E-4D98-9D01-5171CDAEA7D0_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;观景台对面山上的藏文牌子。（在西藏支教过的堂哥说是 “ 宗喀巴大师 ” 的意思&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/F3993ECD-F730-48D0-9665-A7A579823BDE_1_105_c.jpeg&quot; alt=&quot;F3993ECD-F730-48D0-9665-A7A579823BDE_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;F3993ECD-F730-48D0-9665-A7A579823BDE_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;随处可见的雪山观景台。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/FEC58901-817F-4256-9DC5-09773F86832D_1_105_c.jpeg&quot; alt=&quot;FEC58901-817F-4256-9DC5-09773F86832D_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;FEC58901-817F-4256-9DC5-09773F86832D_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;牛吃草。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/78C610FD-D055-41FB-9E5B-6A210B5C998A_1_105_c.jpeg&quot; alt=&quot;78C610FD-D055-41FB-9E5B-6A210B5C998A_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;78C610FD-D055-41FB-9E5B-6A210B5C998A_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;山在脚下，张开手就可以拥抱云彩。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/18C51B3A-F06A-4AAF-AD14-A06A77441A30_1_105_c.jpeg&quot; alt=&quot;18C51B3A-F06A-4AAF-AD14-A06A77441A30_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;18C51B3A-F06A-4AAF-AD14-A06A77441A30_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Windows 10 壁纸。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/57F91480-5954-41E4-8523-CCA23C67E5D1_1_105_c.jpeg&quot; alt=&quot;57F91480-5954-41E4-8523-CCA23C67E5D1_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;57F91480-5954-41E4-8523-CCA23C67E5D1_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;草原哈雷。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/26E1C82F-4DF2-422B-B4E7-88DBBFD9F8FC_1_105_c.jpeg&quot; alt=&quot;26E1C82F-4DF2-422B-B4E7-88DBBFD9F8FC_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;26E1C82F-4DF2-422B-B4E7-88DBBFD9F8FC_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;甘孜的 “ 蒙古包 ”。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/4248D4E4-B38B-4010-BB5E-CE4E62D4E22E_1_105_c.jpeg&quot; alt=&quot;4248D4E4-B38B-4010-BB5E-CE4E62D4E22E_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;4248D4E4-B38B-4010-BB5E-CE4E62D4E22E_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;万岁！&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/5DF49EB3-DE13-4DD2-A873-1DD484900D02_1_105_c.jpeg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;p&gt;到达世界最高城，理塘！（偶遇一个很帅的爆改 3 系&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/0B277FD7-05DD-487B-808C-99E63C0B5C6C_1_105_c.jpeg&quot; alt=&quot;0B277FD7-05DD-487B-808C-99E63C0B5C6C_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;0B277FD7-05DD-487B-808C-99E63C0B5C6C_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;初入稻城亚丁。（要坐 1 个小时大巴车，累的很&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/CADD30F2-F68E-4594-B3B4-C26F7D4FC293_1_105_c.jpeg&quot; alt=&quot;CADD30F2-F68E-4594-B3B4-C26F7D4FC293_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;CADD30F2-F68E-4594-B3B4-C26F7D4FC293_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;跟 Chichi 一起爬央迈勇神山。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/E04D2530-3C15-4F53-8B86-81DBE7BC34E2_1_105_c.jpeg&quot; alt=&quot;E04D2530-3C15-4F53-8B86-81DBE7BC34E2_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;E04D2530-3C15-4F53-8B86-81DBE7BC34E2_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;珍珠海（藏语叫 “ 卓玛拉措 “，其实只是一个大一点的水塘&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191620300.jpeg&quot; alt=&quot;E0964912-B26C-4A6B-AD1F-660253AD207D_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;E0964912-B26C-4A6B-AD1F-660253AD207D_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;亚丁里的雪山，忘记是哪座了。（在亚丁后面相机没电了，拍的比较少&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191622184.jpeg&quot; alt=&quot;791C9072-9EF5-4B3D-A109-32392A4926DA_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;791C9072-9EF5-4B3D-A109-32392A4926DA_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;跟 Chichi 堆的玛尼堆。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191623373.jpeg&quot; alt=&quot;4D284DDE-DEB7-4D14-9664-5F2A2E5BCA84_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;4D284DDE-DEB7-4D14-9664-5F2A2E5BCA84_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;海子山的姊妹湖！&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191624260.jpeg&quot; alt=&quot;314C1B67-C73C-4141-B288-AE8993236075_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;314C1B67-C73C-4141-B288-AE8993236075_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;观景台附近的移动咖啡店。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191625187.jpeg&quot; alt=&quot;AAC4EC2C-D34A-40BB-9EBF-5CFAC9274DF9_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;AAC4EC2C-D34A-40BB-9EBF-5CFAC9274DF9_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;一只狗&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191626270.jpeg&quot; alt=&quot;86205820-3A51-45D1-AE6C-C9E1EEFF7EA9_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;86205820-3A51-45D1-AE6C-C9E1EEFF7EA9_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;初入措普沟（可以感觉出来巴塘官方是真心想发展这个景区，对外游客特别友好，力推！&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191627568.jpeg&quot; alt=&quot;BDAF952D-EDEB-4934-9440-3E145563B0E2_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;BDAF952D-EDEB-4934-9440-3E145563B0E2_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;措普沟圣山脚下的 Tiffany 蓝。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191630465.jpeg&quot; alt=&quot;E5494276-2342-4FC1-924D-31B256556191_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;E5494276-2342-4FC1-924D-31B256556191_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;三小只。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191723943.jpg&quot; alt=&quot;1591697707273_.pic_hd&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1591697707273_.pic_hd&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;色拉山垭口拍的南迦巴瓦峰的日照金山。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/202310191633790.jpeg&quot; alt=&quot;B40C550B-E1A1-4184-927A-9AF93E4F3518_1_105_c&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;B40C550B-E1A1-4184-927A-9AF93E4F3518_1_105_c&lt;/figcaption&gt;&lt;/figure&gt;</content:encoded></item><item><title>阅读、游历和爱情</title><link>https://folay.top/zh/blog/read_travel_love</link><guid isPermaLink="true">https://folay.top/zh/blog/read_travel_love</guid><description>梁永安《阅读、游历和爱情》读书笔记，记录关于自我认知、孤独价值、人格修养和人生意义的思考与感悟。</description><pubDate>Sun, 30 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&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;/p&gt;
&lt;p&gt;在这个过程中 &lt;strong&gt;孤独是很重要的环节&lt;/strong&gt;，年轻的时候扔了很多珍贵的东西，到后来才发现，当年所藐视的、不值一提的东西才是最宝贵的，我们经常将无意义的东西误以为宝，只有在孤独中、在自己的选择里，体会出来的东西才是最真实的。&lt;/p&gt;
&lt;p&gt;本然的自我一定是不完美的，但确实独一无二的，人只有受到阻力时，才能触动自己，反思自己的生命是否真实。一个人一旦通过这种野性的方式触动自己，心里便会一片透亮，感觉特别有价值感、幸福感，而人一旦体会到这种感觉，就不肯放弃了。这种愉悦一定是在路上探索才能体会的，不经一番寒彻骨，怎得梅花扑鼻香。&lt;/p&gt;
&lt;p&gt;希望我们这新一代的人有生而为人的生命的连续性，在他的儿童、少年、青年、中年、老年各个时期，都能活得很真实，是一个由内向外生活的人，而不是由外向内、活在别人眼光里的人。&lt;/p&gt;
&lt;p&gt;书中也有梁老师&lt;strong&gt;关于人格、修养&lt;/strong&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;/p&gt;</content:encoded></item><item><title>在 Colab 搭建 Stable Diffusion</title><link>https://folay.top/zh/blog/stable_diffusion</link><guid isPermaLink="true">https://folay.top/zh/blog/stable_diffusion</guid><description>在Google Colab上搭建Stable Diffusion WebUI的详细教程，包括资源分配、模型配置和服务部署的完整流程。</description><pubDate>Wed, 22 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/sdsm.png&quot; width=&quot;600&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;Form &lt;a href=&quot;https://weirdwonderfulai.art/resources/disco-diffusion-70-plus-artist-studies&quot;&gt;Weird Wonderful Ai Art&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;2023 年 9 月 23 日更新：Colab 官方禁用 Stable Diffusion WebUI，无法解决断网问题，本文方法作废。详情见 &lt;a href=&quot;https://decrypt.co/197428/google-colab-stable-diffusion-web-ui-ban&quot;&gt;https://decrypt.co/197428/google-colab-stable-diffusion-web-ui-ban&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;🎨 Ai 绘画&lt;/h2&gt;
&lt;p&gt;Stable Diffusion 是 stability.ai 于 2022 年发布的深度学习文生图模型，可以在大多数配备有适度 GPU 的电脑硬件上运行。&lt;/p&gt;
&lt;p&gt;Colaboratory 简称 Colab，是 Google Research 团队发布的一种托管式 Jupyter 笔记本服务，可以免费使用 GPU / TPU 计算资源。&lt;/p&gt;
&lt;p&gt;这是一份保姆级的在 Colab 搭建 Stable Diffusion 服务的教程，按照以下步骤操作，即可浅尝 Ai 绘画的魅力。&lt;/p&gt;
&lt;h2&gt;分配资源&lt;/h2&gt;
&lt;p&gt;进入 &lt;a href=&quot;https://colab.research.google.com/github/altryne/sd-webui-colab/blob/main/Stable_Diffusion_WebUi_Altryne.ipynb&quot;&gt;Stable_Diffusion_WebUi_Altryne&lt;/a&gt; Colab 工程。&lt;/p&gt;
&lt;p&gt;点击右上角的&lt;strong&gt;连接&lt;/strong&gt;获取云主机资源并连接运行时。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222142539642.png&quot; alt=&quot;1-image-20230222142539642&quot; loading=&quot;eager&quot; /&gt;&lt;figcaption&gt;1-image-20230222142539642&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;运行 1.0 单元格中的命令可以获主机信息。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222142936172.png&quot; alt=&quot;1-image-20230222142936172&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222142936172&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;一般分配到的都是 Tesla T4 机型，如果人品欧分配到 V100，那可以好好的吹一波了。&lt;/p&gt;
&lt;h2&gt;配置模型&lt;/h2&gt;
&lt;p&gt;首先注册 &lt;a href=&quot;https://huggingface.co/&quot;&gt;Hugging Face&lt;/a&gt; 账号，并生成 WRITE 类型的 &lt;a href=&quot;https://huggingface.co/settings/tokens&quot;&gt;Access Tokens&lt;/a&gt;。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222144043327.png&quot; alt=&quot;1-image-20230222144043327&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222144043327&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;返回到 Colab 界面，展开 &lt;em&gt;1.4 Connect to Google Drive&lt;/em&gt; 单元格，勾选 download_if_missing 并输入 Hugging Face 的 Accsee Token。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222144232989.png&quot; alt=&quot;1-image-20230222144232989&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222144232989&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;设置密码&lt;/h2&gt;
&lt;p&gt;在 &lt;em&gt;2.1 Optional - Set webUI settings and configs before running&lt;/em&gt; 中配置密码。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222144551905.png&quot; alt=&quot;1-image-20230222144551905&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222144551905&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;运行服务&lt;/h2&gt;
&lt;p&gt;将所有单元块收起，依次运行所有单元块部署服务即可。&lt;/p&gt;
&lt;p&gt;运行耗时&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setup stage：5min&lt;/li&gt;
&lt;li&gt;Run the Stable Diffusion webui：1s&lt;/li&gt;
&lt;li&gt;Launch WebUI for stable diffusion：1min&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222144857944.png&quot; alt=&quot;1-image-20230222144857944&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222144857944&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;运行过程中会弹窗请求登录 Google Drive，正常登录并允许即可。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222145720360.png&quot; alt=&quot;1-image-20230222145720360&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222145720360&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;运行完成点击 Public URL 进入 Stable Diffusion WebUI 界面。账号为 wubei，密码为上面自己配置的。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222150541478.png&quot; alt=&quot;1-image-20230222150541478&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222150541478&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;然后就可以尽情的享用了。&lt;/p&gt;
&lt;p&gt;输入 prompt，即可生成对应的图像，比如输入 “ an astronaut in the water ”。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222151132661.png&quot; alt=&quot;1-image-20230222151132661&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222151132661&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;当然，你可以输入更多的 prompt 信息，来生成更加具体准确的图像。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(((masterpiece))),((best quality)), flat chest,((loli)),((one girl)),very long light white hair, beautiful detailed red eyes,aqua eyes,white robe, cat ears,(flower hairpin),sunlight, light smile,blue necklace,see-through,&lt;/p&gt;
&lt;p&gt;杰作，最佳品质，贫乳，萝莉，1个女孩，很长的头发，淡白色头发，红色眼睛，浅绿色眼睛，白色长裙，猫耳，发夹，阳光下，淡淡的微笑，蓝色项链，透明&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/1-image-20230222154010936.png&quot; alt=&quot;1-image-20230222154010936&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;1-image-20230222154010936&lt;/figcaption&gt;&lt;/figure&gt;</content:encoded></item><item><title>Mac多个Git账户共存</title><link>https://folay.top/zh/blog/git_account</link><guid isPermaLink="true">https://folay.top/zh/blog/git_account</guid><description>Mac系统下配置多个Git账户共存的详细教程，包括SSH密钥生成、配置文件和连接测试的完整流程。</description><pubDate>Tue, 14 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;清除配置&lt;/h2&gt;
&lt;p&gt;查看 Git 本地配置&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;清除用户名和邮箱&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--unset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--unset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.email&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;

&lt;h2&gt;生成 ssh-key&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;ssh-keygen&lt;/code&gt; 命令生成 ssh-key，并手动指定 id&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh-keygen&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rsa&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_rsa_xx1@gmail.com&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;xx1@gmail.com&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;生成成功会输出以下内容&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Generating&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public/private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rsa&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pair.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Enter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;which&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;save&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;the&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; (/Users/james/.ssh/id_rsa):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Enter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;passphrase&lt;/span&gt;&lt;span&gt; (empty &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;no&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;passphrase&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Enter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;same&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;passphrase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;again:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Your&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;identification&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;been&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;saved&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/Users/james/.ssh/id_rsa.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Your&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;been&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;saved&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/Users/james/.ssh/id_rsa.pub.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;The&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fingerprint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;SHA256:rwtxjGTJPoV9Mg8lFSf8D4X6jFexWVXKOMRaVyo+RO8&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;xx1@gmail.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;The&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&apos;s randomart image is:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;+---[RSA 3072]----+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|        .o=o+. .*|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|     . + o.=+++o.|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|      * * .==o== |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|     + + *ooo++  |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|      = S .+o+E  |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|       + .. +..  |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|      .   ..     |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|       . .       |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|        o.       |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;+----[SHA256]-----+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;然后继续生成你的其他 Git 账号对应的 ssh-key&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh-keygen&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rsa&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_rsa_xx2@gmail.com&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;xx2@gmail.com&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;信任 ssh-key&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;ssh-add&lt;/code&gt; 命令将生成的 ssh-key 添加到 ssh-agent 信任列表&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh-add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_rsa_xx1@gmail.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh-add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_rsa_xx2@gmail.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;如果遇到 &lt;code&gt;Could not open a connection to your authentication agent.&lt;/code&gt; ，输入&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh-agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;然后重复执行 &lt;code&gt;ssh-add&lt;/code&gt; 即可解决&lt;/p&gt;
&lt;h2&gt;配置公钥&lt;/h2&gt;
&lt;p&gt;复制对应公钥，配置到对应 Git 网站中（GitHub / GitLab）&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pbcopy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_rsa_xx1@gmail.com.pub&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pbcopy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_rsa_xx2@gmail.com.pub&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;这步具体的操作如果有不清楚的可以参考 &lt;a href=&quot;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account&quot;&gt;Adding a new SSH key to your GitHub account&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;配置 config&lt;/h2&gt;
&lt;p&gt;进入 ssh 目录&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;open&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;按照以下规则编辑 config 文件，没有则创建&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;Host&lt;/td&gt;&lt;td&gt;主机&lt;/td&gt;&lt;td&gt;自己起&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hostname&lt;/td&gt;&lt;td&gt;主机名&lt;/td&gt;&lt;td&gt;Git 公有地址，比如 gitub.com / gittee.com&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;IdentityFile&lt;/td&gt;&lt;td&gt;身份文件&lt;/td&gt;&lt;td&gt;rsa 文件路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;User&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;p&gt;编辑完 config 内容如下&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Host&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github1.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Hostname&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;IdentityFile&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_rsa_xx1@gmail.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;xx1@gmail.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Host&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github2.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Hostname&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;IdentityFile&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_rsa_xx2@gmail.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;xx2@gmail.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;测试链接&lt;/h2&gt;
&lt;p&gt;测试 Git 账号是否连接成功，&lt;code&gt;git@&lt;/code&gt; 之后是 config 文件中配置的 Host&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;git@github1.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;git@github2.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;连接成功会有以下输出&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Hi&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;xxx!&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;You&apos;ve successfully authenticated, but GitHub does not provide shell access.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;</content:encoded></item><item><title>《置身事内》读书笔记</title><link>https://folay.top/zh/blog/china_economy</link><guid isPermaLink="true">https://folay.top/zh/blog/china_economy</guid><description>兰小欢《置身事内》读书笔记，记录关于中国地方政府治理、土地财政、债务风险和经济发展模式的深度分析。</description><pubDate>Mon, 02 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;微观&lt;/h1&gt;
&lt;p&gt;在我国，政府不但影响“蛋糕”的分配，也参与“蛋糕”的生产，所以我们不可能脱离政府谈经济。&lt;/p&gt;
&lt;h2&gt;地方政府的权利与事务&lt;/h2&gt;
&lt;p&gt;要理解政府治理和运作的模式，必须首先了解权力和资源在政府体系中的分布规则。这一分布取决于两个重要体制特点：一是&lt;strong&gt;央地关系&lt;/strong&gt;，二是&lt;strong&gt;条块分割&lt;/strong&gt;。央地关系是指整体上中央与地方之间权力要平衡；条块分割是指地方部门要同时接受上级垂直部门和横向地方政府的领导，受到双重制约。&lt;/p&gt;
&lt;p&gt;基于这两个特点，地方政府的权力划分可以从三个视角考察：从&lt;strong&gt;外部性&lt;/strong&gt;的视角，若政府提供的公共服务只影响本地，就可以由本地全权处理，若存在外部性，则需要上级协调；从&lt;strong&gt;信息&lt;/strong&gt;的视角，有信息优势的一方实际权威也会更大，因此上级往往也会花费大量的精力获取下级的信息；从&lt;strong&gt;激励相容&lt;/strong&gt;的视角，与本地发展目标无关或相反的事项更倾向于垂直领导，与本地发展目标相符或本地能受益的事项则倾向于交给本地政府处理。&lt;/p&gt;
&lt;p&gt;根据这些事权划分原则，地方政府在招商引资、发展经济的过程中享有非常广泛的权力，因此可以&lt;strong&gt;深度参与资源的生产与分配过程&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;财务与政府行为&lt;/h2&gt;
&lt;p&gt;要发展经济，地方政府除了有事权还不够，更重要的是财权。1985-1993年，推行财政包干，导致中央财政收入占比逐年下降，于是1994年进行&lt;strong&gt;分税制改革&lt;/strong&gt;，增加了中央的收入，却大大降低了地方的财政资源。&lt;/p&gt;
&lt;p&gt;分税制并没有改变地方政府以经济建设为中心的任务，却减少了其手头可支配的财政来源，地方不得不另谋出路，寻找资金来源，于是轰轰烈烈的&lt;strong&gt;土地财政&lt;/strong&gt;就此登场。土地财政简单来讲就是指地方政府依靠出让土地使用权的收入来维持地方财政支出。&lt;/p&gt;
&lt;p&gt;1998年，发生了两件改变房地产市场的大事：第一，单位停止福利分房，老百姓从此需要自己买房子了，房地产时代自此拉开大幕；第二，修订后的《中华人民共和国土地管理法》开始实施，简言之就是地方政府拥有卖地的决定权，而卖地的收入也归地方所有。地方政府就可以限制商住用地供给，从不断攀升的地价中赚取土地垄断收益。&lt;/p&gt;
&lt;p&gt;一些地区政府实行土地财政。一方面补贴工业用地，招商引资，一方面限制商住用地，房价带动地价。但对于无法依靠土地财政的地区，则出现了财政困难，地区间不平等，需要依靠中央转移支付，并继续进行财政改革。如农村税费改革，乡财县管、省直管县等等。&lt;/p&gt;
&lt;h2&gt;政府投融资与债务&lt;/h2&gt;
&lt;p&gt;土地真正的力量还不在土地财政，而在以土地为抵押而撬动的银行信贷和其他各路资金，土地财政一旦嫁接了资本市场，加上了杠杆，就成了&lt;strong&gt;土地金融&lt;/strong&gt;，能像滚雪球般越滚越大，推动经济飞速扩张。&lt;/p&gt;
&lt;p&gt;政府成立地方政府融资平台，即“城投公司”，以未来的土地收益为抵押撬动大量银行贷款，就可以进行投资以推动城市化和工业化。政府投资主要分两个方向，一是&lt;strong&gt;投资基础建设&lt;/strong&gt;，二是&lt;strong&gt;投资工业&lt;/strong&gt;。投资基础建设就是城投公司开发后再交给政府招商引资，或企业从开发到引资全负责，政府付费使用，这就是“政府与社会资本合作”模式（PPP）。&lt;/p&gt;
&lt;p&gt;但是地方政府通过土地金融推动经济发展的模式仍然存在一些问题。从&lt;strong&gt;微观层面&lt;/strong&gt;，土地金融会增加&lt;strong&gt;地方政府债务风险&lt;/strong&gt;。地方政府债务除了账面的显性负债，更多的是融资平台公司的“隐性负债”。经济向好时可以以土地增值收益作为还款来源，一旦经济遇冷，低价下跌，就可能出现严重的债务问题。&lt;/p&gt;
&lt;p&gt;目前改革的主要措施有用政府公债置换城投公司债务，将城投公司转型为普通国企，避免资金继续流入，约束官员的投资冲动。但是从根本上说，现行体制下，地方官员和普通政府工作人员都拥有非常强的激励去发展经济，这一方面导致投资过度，一方面出现了“官商勾结共同发财式”的系统性腐败。因此深层次的改革应当简政放权，从生产投资型政府向服务型政府转变。&lt;/p&gt;
&lt;h2&gt;工业化中的政府角色&lt;/h2&gt;
&lt;p&gt;我国的经济改革脱胎于计划经济，政府手中掌握大量对产业发展至关重要的资源，如土地、银行、大学和科研机构等，所以必然会以各种方式深度参与工业化进程。&lt;/p&gt;
&lt;p&gt;上面描述的政府投资的第二个方向就是&lt;strong&gt;投资工业&lt;/strong&gt;，由政府对特定企业进行支持和补贴。此外，政府还会设置&lt;strong&gt;产业引导基金&lt;/strong&gt;，成立投资公司或交给市场化的基金管理人运作，作为“母基金”投资其他基金，再通过后者投资未上市公司的股权，从而将更多社会资本引导至战略新兴产业。&lt;/p&gt;
&lt;p&gt;书中以京东方、光伏发展为例讲解了具体的地方政府投资工业的过程，在此不做赘述。&lt;/p&gt;
&lt;h1&gt;宏观&lt;/h1&gt;
&lt;p&gt;上面描述了土地金融模式微观层面上的影响，下面描述&lt;strong&gt;宏观影响&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;城市化与不平衡&lt;/h2&gt;
&lt;p&gt;第一，城市化过程中“&lt;strong&gt;重土地、轻人&lt;/strong&gt;”，从而推高房价，增加居民债务负担，加剧收入差距和贫富差距。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;房价升高&lt;/strong&gt;在中长期看来是因为大城市的建设用地指标被严格管理，跟不上人口流入的速度，导致住房供不应求。房价越高，居民债务负担就越重。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;收入差距&lt;/strong&gt;则是由于城市相关公共服务供给不足，劳动力无法自由流动，低技能人员难以在城市立足。在经济快速增长的过程中，低收入群体对于贫富差距的敏感度没有那么高，但一旦经济放缓，社会对不平等的容忍度将会降低。&lt;/p&gt;
&lt;p&gt;因此&lt;strong&gt;改革办法&lt;/strong&gt;是将重心从土地转移到人，既要让建设用地指标流转起来，打破城市政府对城市住宅用地的垄断，又要改革户籍制度，增加低收入群体的流动性和选择权。&lt;/p&gt;
&lt;h2&gt;债务与风险&lt;/h2&gt;
&lt;p&gt;第二，招商引资过程中“&lt;strong&gt;重规模，重扩张&lt;/strong&gt;”，加重企业债务负担，以及经济整体的债务和金融风险。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;债务的成因&lt;/strong&gt;在于金融危机后我国金融管制的放松，银行更愿意把钱借出去。&lt;/p&gt;
&lt;p&gt;从&lt;strong&gt;债务人企业&lt;/strong&gt;的角度，主要是地方政府融资平台企业、国有企业、房地产企业债务负担较重。它们为了还贷抛售资产，会造成资产价格下跌，而资产价格下跌又会使银行坏账上升，不愿意继续贷款，使企业资金链断裂，从而带来经济衰退。&lt;/p&gt;
&lt;p&gt;从&lt;strong&gt;债权人银行&lt;/strong&gt;的角度，一是规模大，杠杆率高；二是银行负债与资产期限的不匹配会带来流动性风险；三是银行信贷大多以房地产或土地为抵押，因此经济衰退时，银行反而不愿放贷，加速经济下行；四是银行风险会传导至其他金融部门，形成系统风险。银行为了逃避监管设立影子银行，资本在金融机构流转，一旦泡沫破裂，将影响整个金融行业。&lt;/p&gt;
&lt;p&gt;要解决债务问题，可以分为两个部分，一是&lt;strong&gt;偿还已有债务&lt;/strong&gt;，二是&lt;strong&gt;遏制新增债务&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;偿还债务要么变卖资产，要么压缩支出，要么增发货币。后者有三种方法：直接增发、量化宽松、债务货币化。&lt;/p&gt;
&lt;p&gt;要遏制新增债务，除了严控房价上涨、限制土地金融等金融改革，关键是要找到债务增长的根源。企业为什么总是向银行贷款？根据“谁做投资决策，谁承担投资风险”的原则，&lt;strong&gt;我国的投资主要有政府和国企主导，因此风险也是由政府及其控制的金融机构，即银行承担&lt;/strong&gt;。因为银行的风险归根结底是政府的风险。因此限制债务增长的根本措施是资本市场改革，将权力下放给市场，拓宽直接融资渠道，让企业通过股权、债券筹资。&lt;/p&gt;
&lt;h2&gt;国内国际失衡&lt;/h2&gt;
&lt;p&gt;第三，发展战略“&lt;strong&gt;重投资、重生产、轻消费&lt;/strong&gt;”，导致经济结构不平衡。&lt;/p&gt;
&lt;p&gt;对于内部，结构失衡最突出的特征是&lt;strong&gt;消费不足&lt;/strong&gt;，这一方面是因为计划生育、民生支出不足、房价上涨等因素造成的&lt;strong&gt;储蓄偏高&lt;/strong&gt;，一方面也是因为&lt;strong&gt;居民收入份额较低&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在经济发展初期，大量投入资本可以有效实现工业化，推动经济增长，但当经济发展到一定阶段，这种发展模式会造成产能过剩、债务风险（投资流入房地产，推高房价）、贫富差距过大、外部失衡（出口常年大于进口）等一系列问题。&lt;/p&gt;
&lt;p&gt;但是，中美之间的贸易冲突，并非因为中国的出口对美国就业的冲击，而是制造业崛起对美国技术的冲击，再加上美国政治保守主义的兴起。这势必要求我国构建以国内大循环为主体的&lt;strong&gt;双循环模式&lt;/strong&gt;，壮大国内市场，实现“市场—研发—迭代—更大市场”的良性循环。&lt;/p&gt;
&lt;p&gt;这一转型的关键在于&lt;strong&gt;提高居民收入与消费&lt;/strong&gt;。这应当从三个方面入手，一是继续推进城市化，让服务业进一步发展；二是加大民生支出，降低生产性支出；三是发展直接融资渠道，扩宽居民财产性收入。&lt;/p&gt;
&lt;h2&gt;政府与经济发展&lt;/h2&gt;
&lt;p&gt;政府过去发展经济的核心在于引入竞争机制，&lt;strong&gt;由中央协调，地方政府竞争&lt;/strong&gt;。竞争的评价标准就是经济建设。&lt;/p&gt;
&lt;p&gt;这种“&lt;strong&gt;官场+市场&lt;/strong&gt;”的体制优势在于设计了一晰有效的升迁标准，从而给官员提供了极强的激励发展经济。但也会产生地方保护主义、腐败等问题。正如计划经济时代政府干预的特点延续到市场化改革，这一体制也会留下重视经济、忽视民生的路径依赖。&lt;/p&gt;
&lt;p&gt;在市场机制尚不完善的情况下，政府可以作为其补充动员和调配资源，为完善和建设市场经济争取时间。但当市场机制已经相对成熟，政府也要适时改变自己的角色定位，降低政府投资支出，加大民生支出，从&lt;strong&gt;生产型政府转向服务型政府&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这一转变也契合发展经济学的一般视角：对于发展中国家，提高生产率的关键在于学习已知的技术和管理模式，当生产率提升到一定水平后，又要由组织学习模式转向探索创新模式。而无论是怎样的发展模式，不同的国家、不同的地区都会根据具体的政治社会现实而有所不同。&lt;/p&gt;</content:encoded></item><item><title>《亲密关系》读书笔记</title><link>https://folay.top/zh/blog/relationship</link><guid isPermaLink="true">https://folay.top/zh/blog/relationship</guid><description>罗兰·米勒《亲密关系》读书笔记，记录关于亲密关系定义、归属需要、文化影响和个人差异等核心概念的思考。</description><pubDate>Sun, 16 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;写在前面&lt;/h1&gt;
&lt;p&gt;记录阅读 罗兰·米勒 的《亲密关系》过程中的收获。&lt;/p&gt;
&lt;p&gt;人际关系是由多种影响因素构成的，其范围从当前文化的流行时尚到人类种族的基本属性，非常广泛。在这些一般的影响因素之外，还有很多个体独有的影响因素如人格和经验，它们有些是习得的，有些是遗传的。最终，两个来自同一星球，但在很多方面存在一定程度差异的人，开始了他们的互动。互动的结果或许令人沮丧，或许令人满意。&lt;/p&gt;

&lt;h1&gt;亲密关系&lt;/h1&gt;
&lt;p&gt;人际关系种类多样，规格不齐，我们上有父母，还可能下有子女，我们还有朋友和爱人，《亲密关系》书中重点关注的是后两种伙伴关系，而且限制于成人之间。&lt;/p&gt;
&lt;h2&gt;亲密关系的定义&lt;/h2&gt;
&lt;p&gt;亲密关系是一个复杂的概念，包含各种成分，很难去下定义，好在研究者和普通人都认为亲密关系和泛泛之交之间存在六个方面的程度差异。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;了解：广泛而私密的了解&lt;/li&gt;
&lt;li&gt;关心：相互的关心&lt;/li&gt;
&lt;li&gt;相互依赖性：彼此需要和影响对方的程度时频繁、强烈、多样且持久的&lt;/li&gt;
&lt;li&gt;相互一致性：自我接纳他人的程度是否高度重合&lt;/li&gt;
&lt;li&gt;信任：相信对方会善待和尊重自己&lt;/li&gt;
&lt;li&gt;忠诚：希望关系能地老天长，并不惜时间、人力和物力的消耗&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;归属需要&lt;/h2&gt;
&lt;p&gt;为什么人们需要亲密关系呢？&lt;/p&gt;
&lt;p&gt;亲密关系对于人类的重要性主要体现在归属需要上，归属需要是人类长期演化的产物，逐渐成为所有人类共同的自然倾向，让我们觉得与和自己有关联的人的正常社会交往是必不可少的，是对持续关爱和包容的一种需要。&lt;/p&gt;
&lt;h1&gt;文化的影响&lt;/h1&gt;
&lt;p&gt;文化标准是人们建立人际关系的基石，它影响着人们对人际关系的期望，限定了正常的人际关系模式。&lt;/p&gt;
&lt;p&gt;就拿最近普遍流行的同居现象来说，现在许多高中生认为情侣未婚同居是个“好主意”，因为他们能据此考察彼此是否真正能“和睦相处”（Bachman etal.，2001）。这种态度使未婚同居看上去很有道理，好像是个不错的选择。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/iShot_2022-10-17_00.39.10.png&quot; width=&quot;600&quot; height=&quot;350&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;然而，如果人们并没有切实的结婚计划，未婚同居并不能确保随后的婚姻幸福美满；相反，同居增加了夫妻离婚的危险。如上图的研究结果所示，随着时间的推移，同居情侣结婚的可能性逐渐降低，但分手的可能性却不下降。&lt;/p&gt;
&lt;p&gt;总的说来，草率同居原本用来测试伴侣能否和睦共处，却好像会损害人们对婚姻的积极态度和维持婚姻的决心，这种态度和决心是幸福婚姻的支柱（Rhoades et al.，2009）。&lt;/p&gt;
&lt;h1&gt;个人经历的影响&lt;/h1&gt;
&lt;p&gt;依恋类型不受基因影响，是在与生俱来的个体特征和养育水平的影响下塑造的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安全型：对亲密关系和相互依赖安心、乐观、好交际&lt;/li&gt;
&lt;li&gt;痴迷型：对有损坏亲密关系的任何威胁都不安和警惕、嫉妒、贪婪&lt;/li&gt;
&lt;li&gt;恐惧型：自立、漠视亲密关系、冷淡、独立&lt;/li&gt;
&lt;li&gt;疏离型：害怕被遗弃、不信任他人、猜忌多疑、害羞&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们幼时对人际交往价值和他人是否可信的观念，起源于我们与照料者的交往，由于运气的好坏，我们就此走向了信任或恐惧的亲密关系之路。这段历程永远不会停止，同行者随后给予的阻碍或帮助会改变我们亲密关系的方向和进程。视乎人际交往经验的不同，我们习得的依恋类型既可随时间发生变化，也可永久保持稳定。&lt;/p&gt;
&lt;h1&gt;个体差异的影响&lt;/h1&gt;
&lt;h2&gt;性别差异&lt;/h2&gt;
&lt;p&gt;两性群体差异确实存在，但远低于同性不同个体间的差异程度。&lt;/p&gt;
&lt;p&gt;两性性别内的行为和观点差异通常远大于两性之间的平均差异。男性较女性更能接受随意、短暂的性关系（Peterson &amp;amp; Hyde，2010），这未必表示所有男性都喜欢随意的性关系。&lt;/p&gt;
&lt;p&gt;有些男人喜欢与陌生人发生性行为，但也有些男人根本不喜欢这样做，这两组男性在性生活上的相似程度远不如男性和女性的平均水平。换句话说，尽管在性放任上存在两性差异，一位非常性放任的男人与女性性放任的平均水平的差别，远低于他与另一位性保守的男人在性放任上的差别。&lt;/p&gt;
&lt;p&gt;书中一则描述刻板化印象的笑话很有趣。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;怎样吸引女人：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;夸赞她，依偎她，亲吻她，爱抚她，热爱她，安慰她，保护她，拥抱她，不惜千金买笑，奉出美酒佳肴，烦心的时候听她唠叨，微恙的时候悉心照料，永远和她待在一起，永远支持她的意见，为她走遍天涯海角。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;怎样吸引男人：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;脱光衣服，带上啤酒。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;实际上，两性对亲密关系的期望差异很小，他们根本不是“相反的”两类人（Hyde，2007）。如果认为男女两性差异很大，当面临冲突时（这无法避免）不太可能去努力修复自己与异性的亲密关系。认为异性是来自另一世界的外星人不仅是错误的，更是有害的，它阻碍了对伴侣观点的理解，妨碍了双方协作解决问题。&lt;/p&gt;
&lt;h2&gt;性认同差异&lt;/h2&gt;
&lt;p&gt;性认同差异指的是由文化和教育引起的两性在社会性和心理上的差异，或者叫社会性别（Wood &amp;amp; Eagly，2009）。&lt;/p&gt;
&lt;p&gt;性认同最好的例子是性别角色（gender roles），即社会文化所期待的男女两性应有的“正常”行为模式，下面是常见的性别角色分类，社会期望和鼓励男人具有工具性，女人具有表达性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工具性：自信、独立、有抱负、领导力、果敢，约占 25%&lt;/li&gt;
&lt;li&gt;表达性：热情、温柔、有同情心、亲切、敏感，约占 25%&lt;/li&gt;
&lt;li&gt;跨类型：约占 35%&lt;/li&gt;
&lt;li&gt;未分化：约占 15%&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;人格&lt;/h2&gt;
&lt;p&gt;大五人格特质可以很好地区分人们在诸多方面（如行为、思维和情感等）的差异（McCrae &amp;amp; Costa，2010）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开放性：富有想象力、不墨守成规、艺术气质，相对应的是拘泥、僵化和教条。&lt;/li&gt;
&lt;li&gt;外倾性：开朗、合群、热情、喜欢社交，相对应的是谨慎、内敛及害羞。&lt;/li&gt;
&lt;li&gt;尽责性：勤劳、可依赖、有序，相对应的是不可靠、粗心大意。&lt;/li&gt;
&lt;li&gt;宜人性：同情心、合作性、对人信任，相对应的是易怒、暴躁和充满敌意。&lt;/li&gt;
&lt;li&gt;神经质：善变、容易担忧、焦虑和愤怒的程度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重要性从低到高排列的，大五特质中最重要的是那个有消极作用的特质：神经质（Malouff et al.，2010）。神经质的人容易发怒和焦虑，这些不良倾向往往会引起人际摩擦、悲观情绪和争执（Suls &amp;amp; Martin，2005）。&lt;/p&gt;
&lt;h2&gt;自尊&lt;/h2&gt;
&lt;p&gt;自尊就是人际交往中的自我评价，可以看作一种 “ 社会关系测量仪 ”，如果他人积极地对待我们并看重与我们的关系，自尊水平就高；如果我们不能吸引别人的关注，自尊水平就低。&lt;/p&gt;
&lt;p&gt;我们人类是高度社会化的动物，如果他人不喜欢我们，我们要喜欢自己非常困难（的确，这样做很不现实）。大多数情况下，如果不能从他人那里获得足够的接纳和欣赏，长期处在低自尊的人就会形成负面的自我评价。&lt;/p&gt;
&lt;p&gt;值得一提的是自尊在亲密关系中的影响：低自尊的人有时低估伴侣对他们的爱，以致损害亲密关系（Murray et al.，2001）。&lt;/p&gt;
&lt;p&gt;我们都需要在与他人的联系和自我保护间保持平衡，但低自尊的人总把他们脆弱的自尊心置于亲密关系之上。低自尊者的自我怀疑和敏感脆弱使他们从无数的琐事中制造出堆积如山的问题。他们错误地以为爱情之路上的磕磕碰碰是伴侣拒绝承诺的不祥之兆。然后，又表现出令人反感、自我打击式的伤害和愤怒，完全隔断了自己渴望的伴侣的安慰。相形之下，高自尊者对同样的小磕绊完全不以为意，信心十足地期待伴侣对自己的接纳和正面评价。&lt;/p&gt;
&lt;h1&gt;人类本性的影响&lt;/h1&gt;
&lt;p&gt;数千年来人类生存所面临的环境压力遗留下了精神和情感的痕迹。某些遗留下的情感和行为反应源自我们的远祖，在现代已经没有必要了，但前人生存的这些遗迹不可磨灭地刻入了我们的性格，使得每个人都表现出一定的倾向性。&lt;/p&gt;
&lt;p&gt;演化心理学的三个假设：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性选择使人类成为今天这样的物种。&lt;/li&gt;
&lt;li&gt;两性之所以存在差异，只是因为某种程度上他们在过去面临着不同的繁殖困境（养育投入）。&lt;/li&gt;
&lt;li&gt;文化影响决定了演化形成的行为模式是否具有适应性，并且文化的变化比演化快得多（父系不确定）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;人际互动的影响&lt;/h1&gt;
&lt;p&gt;人际关系常常大于它各部分相加的总和，这便是人际关系的最后一个构成要素 “ 互动 ” 的影响。&lt;/p&gt;
&lt;p&gt;比如拿伴侣关系中的信任程度来说，信任是双向的过程，同时收到你和伴侣双方性情的影响，来源于你每天和伴侣不断付出以及不断接受的动态过程，换而言之，信任是流动的过程而非静止不变的事物，它在你所有的人际关系中时起时落。&lt;/p&gt;
&lt;h1&gt;人际关系的消极面&lt;/h1&gt;
&lt;p&gt;我们不得不承认人际关系也有一些潜在的代价，有时我们和他人打交道也会带来不幸和痛苦。&lt;/p&gt;
&lt;p&gt;当人们与他人接近时，可能害怕自己最在乎的秘密被人揭露或利用。他们还可能担忧伴随相互依赖而来的自主性和自我控制的丧失（Baxter，2004）。他们或许还担心会被自己所依赖的人抛弃。他们认识到人际关系可能存在欺骗，人们有时还会混淆了性和爱（Firestone &amp;amp; Catlett，1999）。实际上，大多数人（56%）在过去的5年中人际关系都曾陷入困境（Levitt et al.，1996）。&lt;/p&gt;
&lt;p&gt;那么为什么还要冒这种风险呢？书中的回答很是浪漫。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;因为我们人类是社会化的动物，我们需要彼此。没有与他人的亲密联系，我们就会枯萎和死亡 。&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Ring_layout：Flutter 环形布局实现</title><link>https://folay.top/zh/blog/ring_layout</link><guid isPermaLink="true">https://folay.top/zh/blog/ring_layout</guid><description>Flutter环形布局组件的数学原理和实现方案，包括子元素位置计算、半径推导和CustomMultiChildLayout的应用。</description><pubDate>Sun, 24 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;环形布局的定义&lt;/h2&gt;
&lt;p&gt;如果存在一个圆 A 和若干个圆 a，圆 a 皆于圆 A 相交，圆 a 的圆心皆位于圆 A 上，且圆 a 间的 &lt;a href=&quot;https://baike.baidu.com/item/%E5%9C%86%E5%BF%83%E8%B7%9D/9556681?fr=aladdin&quot;&gt;圆心距&lt;/a&gt; 相等。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/iShot2022-04-24_22.15.57.png&quot; width=&quot;400&quot; height=&quot;400&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;即环形布局应当满足以下两个属性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;子 widget的中点到容器圆心的距离保持一致。&lt;/li&gt;
&lt;li&gt;相邻子 widget 中点的间距保持一致。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根据以上性质我们可以根据数学公式计算出 &lt;strong&gt;圆 a 相对于圆 A 的位置&lt;/strong&gt; ，这是实现环形布局的关键信息。&lt;/p&gt;
&lt;p&gt;在上面的定义中并未提及圆 a 的半径关系，实际上圆 a 的半径是可以不一致的，把圆 a 看作子元素的 &lt;a href=&quot;https://baike.baidu.com/item/%E5%A4%96%E5%88%87%E5%9C%86/4186184?fr=aladdin&quot;&gt;外切圆&lt;/a&gt;，在复杂的生产环境中子元素的外切圆半径往往是不一致的，所以我们还需要确定 &lt;strong&gt;圆 a 的最大半径&lt;/strong&gt; 。&lt;/p&gt;
&lt;h2&gt;计算子元素的位置&lt;/h2&gt;
&lt;h3&gt;数学推导&lt;/h3&gt;
&lt;p&gt;要确定圆 a 相对于圆 A 的位置，首先要计算 &lt;strong&gt;圆心 a 相对于圆心 A 的偏移量&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;设圆心 A 坐标为 &lt;span&gt;&lt;span&gt;(x0,y0)(x_0, y_0)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 、半径为 &lt;span&gt;&lt;span&gt;rr&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;、圆心 a 坐标为 &lt;span&gt;&lt;span&gt;(x1,y1)(x_1, y_1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; ，圆心 A 和圆心 a 的连线和坐标系横轴的夹角角度为 &lt;span&gt;&lt;span&gt;θ\theta&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;θ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/iShot2022-04-25_15.56.56.png&quot; width=&quot;400&quot; height=&quot;400/&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;圆心 a 坐标 &lt;span&gt;&lt;span&gt;(x1,y1)(x_1, y_1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为圆心 A 坐标 &lt;span&gt;&lt;span&gt;(x0,y0)(x_0, y_0)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 加上相对坐标系轴上的偏移量。&lt;/p&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;x1=x0+r×cos(θ)x_1 = x_0 + r \times cos(\theta)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;cos&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;θ&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;y1=y0+r×sin(θ)y_1 = y_0 + r \times sin(\theta)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;θ&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;h3&gt;代码实现&lt;/h3&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// 计算圆心a相对于圆心A的偏移量&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;///&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param centerPoint 圆心A的坐标&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param radius 圆A的半径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param count 圆a的数量&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param which 圆a的序号&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param initAngle 起始位置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param direction 排列方向&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Offset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_getChildCenterOffset&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;Offset&lt;/span&gt;&lt;span&gt; circleCenter,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; radius,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; count,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; which,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; firstAngle,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; direction,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 扇形弧度&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; radian &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_radian&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;360&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; count);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 处理起始位置偏移和排列方向&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; radianOffset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_radian&lt;/span&gt;&lt;span&gt;(firstAngle &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; direction);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; circleCenter.dx &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; radius &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cos&lt;/span&gt;&lt;span&gt;(radian &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; which &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; radianOffset);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; y &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; circleCenter.dy &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; radius &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sin&lt;/span&gt;&lt;span&gt;(radian &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; which &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; radianOffset);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Offset&lt;/span&gt;&lt;span&gt;(x, y);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;计算子元素的半径&lt;/h2&gt;
&lt;h3&gt;数学推导&lt;/h3&gt;
&lt;p&gt;为了满足子元素环形排列的需要，最大子元素的外切圆上限需为 &lt;span&gt;&lt;span&gt;90∘90^\circ&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;∘&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 扇形的 &lt;a href=&quot;https://baike.baidu.com/item/%E5%86%85%E5%88%87%E5%9C%86&quot;&gt;内切圆&lt;/a&gt;，如下图所示。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/iShot2022-04-24_23.42.54.png&quot; width=&quot;400&quot; height=&quot;400&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;设扇形半径为 &lt;span&gt;&lt;span&gt;RR&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;R&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;、扇形圆心角为 &lt;span&gt;&lt;span&gt;α\alpha&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;、扇形内切圆半径为 &lt;span&gt;&lt;span&gt;rr&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/iShot2022-04-25_20.50.01.png&quot; width=&quot;400&quot; height=&quot;400&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;最大子元素半径推导过程如下。&lt;/p&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;sin(α2)=rR−rsin(\frac{\alpha}{2}) = \frac{r}{R - r}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;−&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;r&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;r=(R−r)×sin(α2)r = (R - r) \times sin(\frac{\alpha}{2})&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;−&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;r=R×sin(α2)−r×sin(α2)r = R \times sin(\frac{\alpha}{2}) - r \times sin(\frac{\alpha}{2})&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;−&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;r+r×sin(α2)=R×sin(α2)r + r \times sin(\frac{\alpha}{2}) = R \times sin(\frac{\alpha}{2})&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;r=R×sin(α2)1+sin(α2)r = \frac{R \times sin(\frac{\alpha}{2})}{1 + sin(\frac{\alpha}{2})}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;h3&gt;代码实现&lt;/h3&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// 计算圆a的半径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;///&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param radius 圆A的半径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param angle 扇形的角度&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_getChildRadius&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; radius, &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; angle) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 扇形角度大于180度，只可以放置一个。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (angle &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;180&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; radius;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;/// 扇形最大内切圆公式，见公式推导。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; radius &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sin&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_radian&lt;/span&gt;&lt;span&gt;(angle &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sin&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_radian&lt;/span&gt;&lt;span&gt;(angle &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// 计算弧度&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;///&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/// @param angle 角度&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_radian&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; angle) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; pi &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;180&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; angle;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;实现&lt;/h2&gt;
&lt;p&gt;我们选择使用 &lt;code&gt;CustomMultiChildLayout&lt;/code&gt; 实现环形布局的功能，看下官网的定义。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://oldjii.github.io/pic/iShot2022-04-24_22.24.15.png&quot; alt=&quot;&quot; loading=&quot;eager&quot; /&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;“ CustomMultiChildLayout is appropriate when there are complex relationships between the size and positioning of multiple widgets. ”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以用 &lt;code&gt;CustomMultiChildLayout&lt;/code&gt; 实现再合适不过，效果如下。&lt;/p&gt;
&lt;div&gt;
&lt;img src=&quot;https://oldjii.github.io/pic/屏幕录制2022-06-15-上午10.41.11.gif&quot; height=&quot;400&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;完整代码已上传至 &lt;a href=&quot;https://pub.dev&quot;&gt;pub.dev&lt;/a&gt;，这里仅截取 &lt;code&gt;RingLayout&lt;/code&gt; 的部分代码。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ring_layout： &lt;a href=&quot;https://pub.dev/packages/ring_layout&quot;&gt;https://pub.dev/packages/ring_layout&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RingLayout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;StatelessWidget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt;&amp;gt; children;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; initAngle;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; reverse;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; radiusRatio;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RingLayout&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; key,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.children,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.reverse &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.radiusRatio &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.initAngle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})  &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; radiusRatio &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; radiusRatio &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; initAngle &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; initAngle &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;360&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;super&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; key);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;@override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CustomMultiChildLayout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;delegate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_RingDelegate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; children.length,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;initAngle&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; initAngle,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reverse&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; reverse,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;radiusRatio&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; radiusRatio),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; children.length; i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;LayoutId&lt;/span&gt;&lt;span&gt;(id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; i, child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; children[i])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;</content:encoded></item><item><title>《自私的基因》读书笔记</title><link>https://folay.top/zh/blog/gene</link><guid isPermaLink="true">https://folay.top/zh/blog/gene</guid><description>《自私的基因》读书笔记，记录道金斯关于基因自私性的主要观点，以及人类如何通过教育来对抗自私基因的思考。</description><pubDate>Thu, 24 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;初开始听说&lt;a href=&quot;https://zh.wikipedia.org/zh-hans/%E8%87%AA%E7%A7%81%E7%9A%84%E5%9F%BA%E5%9B%A0&quot;&gt;《自私的基因》&lt;/a&gt;这本书，是在 21 年末的时候，那时候刚从小米离职，与新公司约定的入职时间稍晚了些，计划的是来一场说走就走的离职旅行、散散心，可事与愿违，年底疫情的爆发打乱了所有安排。&lt;/p&gt;
&lt;p&gt;画地为牢的居家隔离生活总是漫长的，每天都要看几集&lt;a href=&quot;https://baike.baidu.com/item/%E5%9C%86%E6%A1%8C%E6%B4%BE/19543072?fr=aladdin&quot;&gt;《圆桌派》&lt;/a&gt;消磨时光，其中印象最深刻的是第五季的第十一期，这期的嘉宾是华大基因的 CEO 尹烨，一个理科生、三个文科生围绕 “ 基因 ” 展开了热火朝天的讨论，从 “ 体外胚胎技术 ” 到 “ 人体细胞更新 ”，从 “ 忒修斯之船 ” 到 “ 道金斯的自私的基因 ”，每个话题都充斥着思维碰撞的火花，节目的最后尹烨便推荐了这本书 -《自私的基因》。&lt;/p&gt;
&lt;p&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;/p&gt;
&lt;p&gt;&lt;strong&gt;“ 明显的利他行为实际上是伪装起来的自私行为。 ”&lt;/strong&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;strong&gt;“ 我们具备足够的力量去抗拒我们那些与生俱来的自私基因。我们也可以抗拒那些灌输到我们脑子里的自私觅母。我们甚至可以讨论如何审慎地培植纯粹的、无私的利他主义，这种利他主义在自然界里是没有立足之地的，在世界整个历史上也是前所未有的。我们是作为基因机器而被建造的，是作为觅母机器而被培养的，但我们具备足够的力量去反对我们的缔造者。在这个世界上，只有我们，我们人类，能够反抗自私的复制基因的暴政。 ”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;就像尹烨在节目结尾说道：“ 如果人类是一组代码，那我相信人类的代码中有爱 ”。&lt;/p&gt;
&lt;p&gt;你我，共勉。&lt;/p&gt;</content:encoded></item><item><title>Hugo中支持LaTeX的数学表达式</title><link>https://folay.top/zh/blog/hugo_mathjax</link><guid isPermaLink="true">https://folay.top/zh/blog/hugo_mathjax</guid><description>在Hugo博客中集成MathJax支持LaTeX数学表达式的配置方法，包括JavaScript代码和CSS样式的完整实现。</description><pubDate>Sat, 18 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hugo 默认是不支持显示 LaTeX 风格的数学表达式的，Markdown 中的数学表达式语法在 Hugo 中默认并不会被识别。&lt;/p&gt;
&lt;p&gt;在 Hugo 中引入 MathJax 即可解决该问题，&lt;a href=&quot;https://www.mathjax.org&quot;&gt;MathJax&lt;/a&gt; 是一个适用于所有浏览器的 JavaScript 数学显示引擎。&lt;/p&gt;
&lt;p&gt;引入方法很简单，在文章的模版的 HTML 的中添加以下内容即可。&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://cdn.bootcss.com/mathjax/2.7.3/MathJax.js?config=TeX-AMS-MML_HTMLorMML&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;MathJax.Hub.Config({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tex2jax: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;inlineMath: [[&lt;/span&gt;&lt;span&gt;&apos;$&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&apos;$&apos;&lt;/span&gt;&lt;span&gt;], [&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;(&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;)&apos;&lt;/span&gt;&lt;span&gt;]],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;displayMath: [[&lt;/span&gt;&lt;span&gt;&apos;$$&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&apos;$$&apos;&lt;/span&gt;&lt;span&gt;], [&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\[\[&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\]\]&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;processEscapes: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;processEnvironments: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;skipTags: [&lt;/span&gt;&lt;span&gt;&apos;script&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;noscript&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;style&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;textarea&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;pre&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TeX: { equationNumbers: { autoNumber: &lt;/span&gt;&lt;span&gt;&quot;AMS&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;extensions: [&lt;/span&gt;&lt;span&gt;&quot;AMSmath.js&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;AMSsymbols.js&quot;&lt;/span&gt;&lt;span&gt;] }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;MathJax.Hub.Queue(function() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// Fix &amp;lt;code&amp;gt; tags after MathJax finishes running. This is a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// hack to overcome a shortcoming of Markdown. Discussion at&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// https://github.com/mojombo/jekyll/issues/199&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;var all &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MathJax.Hub.&lt;/span&gt;&lt;span&gt;getAllJax&lt;/span&gt;&lt;span&gt;(), i;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;(i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; all.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;all[i].SourceElement().parentNode.className += &apos; has-jax&apos;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;style&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;code.has-jax {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;font: inherit;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;font&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;size: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;background: inherit;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;border: inherit;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;color: #&lt;/span&gt;&lt;span&gt;515151&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;要注意的是需要确保该 HTML 必定被 Hugo 框架加载，我这里是添加到了 &lt;code&gt;layouts/partials/&lt;/code&gt; 目录下的 &lt;code&gt;footer.html&lt;/code&gt;文件中，因为我确定该文件必定包含在网站的每个页面中。&lt;/p&gt;
&lt;p&gt;实际上单纯引入 MathJax 并不需要如此多行代码，多余的部分是为了解决 Markdown 和 LaTeX 中对于划线 _ 的不同定义带来的问题，详情可以参考 &lt;a href=&quot;https://www.gohugo.org/doc/tutorials/mathjax/&quot;&gt;这篇文章&lt;/a&gt; 。&lt;/p&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;D(x)={lim⁡x→0axb+c,x&amp;lt;3π,x=3∫a3bxij+e2dx,x&amp;gt;3D(x) = \begin{cases}
\lim\limits_{x \to 0} \frac{a^x}{b+c}, &amp;amp; x&amp;lt;3 \\
\pi, &amp;amp; x=3 \\
\int_a^{3b}x_{ij}+e^2 \mathrm{d}x,&amp;amp; x&amp;gt;3 \\
\end{cases}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;D&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;⎩&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;⎨&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;⎧&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;lim&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;π&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;∫&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;ij&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;lim⁡x→∞x222−∫15xdx+∑n=120n2=∏j=13yj+lim⁡x→−2x−2x\lim_{x \to \infty} x^2_{22} - \int_{1}^{5}x\mathrm{d}x + \sum_{n=1}^{20} n^{2} = \prod_{j=1}^{3} y_{j}  + \lim_{x \to -2} \frac{x-2}{x}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;span&gt;∞&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;lim&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;22&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;−&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;∫&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;20&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;j&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;∏&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;j&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;span&gt;−&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;lim&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;−&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;好了，开心的在 Hugo 中使用 LaTeX 吧。&lt;/p&gt;</content:encoded></item><item><title>HLS直播协议m3u8</title><link>https://folay.top/zh/blog/m3u8</link><guid isPermaLink="true">https://folay.top/zh/blog/m3u8</guid><description>HLS流媒体传输协议的详细介绍，包括m3u8文件格式解析、Master Playlist和Media Playlist的结构说明。</description><pubDate>Sat, 25 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;为了方便理解，会按照“流媒体传输协议”、“HLS”、“M3U8”的顺序来介绍。&lt;/p&gt;
&lt;p&gt;三者关系：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HLS是一种流媒体传输协议&lt;/li&gt;
&lt;li&gt;M3U8是HLS传输内容中的一部分&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;流媒体传输协议&lt;/h1&gt;
&lt;h2&gt;常见的流媒体传输协议&lt;/h2&gt;
&lt;p&gt;流媒体就是以数据流的方式，实时发布音频、视频多媒体内容的媒体形式，关键技术在于&lt;code&gt;流式传输&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;流媒体传输协议就是用来定义如何流式传输的，设计、制定了流媒体服务器和客户端通讯的方式。&lt;/p&gt;
&lt;p&gt;主流的流媒体传输协议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RTMP（Real Time Protocol）：基于 TCP 的 FLV 分块 message 传输协议，用于 Flash 客户端。&lt;/li&gt;
&lt;li&gt;HTTP-FLV：基于 HTTP 长连接的 FLV 分块 tag 传输协议，可用于点播和直播场景。&lt;/li&gt;
&lt;li&gt;HLS（HTTP Live Streaming）：基于 HTTP，由 Apple 推出的 MP4 分片传输协议，可以用于点播、直播，每次下载一次分片都需要发生一次 HTTP 请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;本文只详细介绍 HLS，不涉及 RTMP 与 RTSP&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;流媒体加密原理&lt;/h2&gt;
&lt;p&gt;大多数流媒体传输协议都可以分为拆分、加密两部分。&lt;/p&gt;
&lt;p&gt;拆分是 将完整的视频流拆分为连续的视频片段，不同的传输协议的区别在于拆分片段的大小、视频容器的格式不同。&lt;/p&gt;
&lt;p&gt;加密是 对每段视频片段进行加密，使用对称加密算法，在服务端加密，在客户端解密，且通过一定手段限制解密密钥的获取。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一般使用 AES 加密算法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;为什么是对称加密？&lt;/h2&gt;
&lt;p&gt;对称加密效率相对较高，非对称加密效率相对较低，但是更安全。流媒体场景对实时性的要求很高，而且数据量也很大，所以选用效率相对较高的对称加密算法。&lt;/p&gt;
&lt;p&gt;类似的场景还有很多，比如 HTTPS 的请求过程，内容传输为了效率选用对称加密（TLS），证书校验为了安全选用非对称加密（SSL）。&lt;/p&gt;
&lt;h1&gt;HLS&lt;/h1&gt;
&lt;p&gt;HLS 全称 HTTP Live Streaming， 是由 Apple 提出的基于 HTTP 的流媒体传输协议，用于实时音视频流的传输，目前已被广泛应用与视频点播、直播场景。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;参考资料：&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008332-CH1-SW1&quot;&gt;HTTP Live Streaming Document&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;工作原理&lt;/h2&gt;
&lt;p&gt;完整的 HLS 架构可以划分为 3 个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务器 Server：负责视频流的编码、切割为连续的 MPEG-TS 格式的视频片段，并提供配套的 M3U8 类型的媒体列表文件和索引文件。&lt;/li&gt;
&lt;li&gt;分发组件 CDN：由标准的网络服务器组成，负责接收客户端的请求并分发资源。&lt;/li&gt;
&lt;li&gt;客户端 Client：先下载 m3u8 索引文件，根据带宽等字段选择合适的 m3u8 媒体播放列表文件下载，按顺序下载列表中的所有 ts 视频片段文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完整的 HLS 的&lt;strong&gt;过程&lt;/strong&gt;可以参考下图：&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://i.loli.net/2021/09/16/6Bp37ZFcPfqbAvy.png&quot; alt=&quot;&quot; loading=&quot;eager&quot; /&gt;&lt;/figure&gt;
&lt;p&gt;大体可以划分为 6 个阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;采集媒体源&lt;/li&gt;
&lt;li&gt;媒体编码器（Media encoder） 对媒体源进行编码&lt;/li&gt;
&lt;li&gt;编码后以MPEG-2的传输串形式传递给切片器&lt;/li&gt;
&lt;li&gt;切片器（Steam Segmenter）将媒体切割为若干 &lt;code&gt;Media Segment&lt;/code&gt;，并创建配套的媒体列表文件 &lt;code&gt;Media Playlist&lt;/code&gt; 以及索引文件 &lt;code&gt;Master Platlist&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;上传：将资源上传到 HTTP 服务器。&lt;/li&gt;
&lt;li&gt;播放：客户端请求播放。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;组成结构&lt;/h2&gt;
&lt;p&gt;经过上面第 4 步骤的加工可以形成完整的结构，由 &lt;code&gt;Master Playlist&lt;/code&gt;、&lt;code&gt;Media Playlist&lt;/code&gt;、&lt;code&gt;Media Segment&lt;/code&gt; 构成，关系结构如图。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://i.loli.net/2021/09/16/F7fcQpGuyiNUYH8.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;p&gt;完整的 HLS 结构由两部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;m3u8 类型的 Master Playlist 文件：其中会提供若干根据带宽等字段区分的 Media Playlist 的请求链接。&lt;/li&gt;
&lt;li&gt;m3u8 类型的 Media Playlist 文件：其中会有视频的基本信息和若干 Media Segment 的请求链接，这些片段就组成了完整的视频。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Media Segment 就是单纯的 ts 格式的视频文件，并无任何描述信息，可以单独使用播放器进行播放。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;M3U8 是 Unicode 版本的 M3U，8 代表使用的是 UTF-8 编码，M3U 和 M3U8 都是多媒体列表的文件格式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;M3U8&lt;/h1&gt;
&lt;p&gt;M3U8 描述文件中由各种描述字段构成，下面解释部分主要字段的含义。&lt;/p&gt;
&lt;p&gt;我在网上随便找的一个 m3u8 视频的链接：&lt;code&gt;https://mgtv-com.jjyl12349.com/20210519/fXE0kuJ7/index.m3u8&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;请求该链接的返回结果为一个 m3u8 文件，也就是 Master Playlist 文件。&lt;/p&gt;
&lt;h2&gt;Master Playlist&lt;/h2&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXTM3U&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;STREAM&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;INF&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;BANDWIDTH&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;150000&lt;/span&gt;&lt;span&gt;,RESOLUTION&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;416x234&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;20210519&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;fXE0kuJ7&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;150kb&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;hls&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;index.m3u8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;STREAM&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;INF&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;BANDWIDTH&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;150000&lt;/span&gt;&lt;span&gt;,RESOLUTION&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;416x234&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;20210519&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;fXE0kuJ7&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;150kb&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;hls&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;index.m3u8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;STREAM&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;INF&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;BANDWIDTH&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1000000&lt;/span&gt;&lt;span&gt;,RESOLUTION&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1280x720&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;20210519&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;fXE0kuJ7&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;1000kb&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;hls&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;index.m3u8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;字节解释&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EXTM3U：表示该文件为m3u8文件，每个M3U文件都是以EXTM3U开头&lt;/li&gt;
&lt;li&gt;EXT-X-STREAM-INF：表示一个备份源，并提供备份源的相关信息
&lt;ul&gt;
&lt;li&gt;BANDWIDTH：表示每秒传输的比特数，即带宽&lt;/li&gt;
&lt;li&gt;RESOLUTION：表示备份源的最佳像素方案&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们根据 BANDWIDTH、RESOLUTION 等信息选取合适的 Media Playlist 的请求链接，并将链接与视频链接的域名结合，即可得到完整的链接。&lt;/p&gt;
&lt;p&gt;比如，BANDWIDTH 为 1000kb、RESOLUTION 为 1280x720 的备用源的请求链接为：&lt;code&gt;https://mgtv-com.jjyl12349.com/20210519/fXE0kuJ7/1000kb/hls/index.m3u8&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;请求该链接的返回结果也为一个 m3u8 文件，也就是 Media Playlist 文件。&lt;/p&gt;
&lt;h2&gt;Media Playlist&lt;/h2&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXTM3U&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;VERSION&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;TARGETDURATION&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;PLAYLIST&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;TYPE&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;VOD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;MEDIA&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;SEQUENCE&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;KEY&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;METHOD&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;128&lt;/span&gt;&lt;span&gt;,URI&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://mgtv-com.ycshengwang.com/20210519/fXE0kuJ7/1000kb/hls/key.key&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXTINF&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;https&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;//mgtv-com.ycshengwang.com/20210519/fXE0kuJ7/1000kb/hls/mDHy0Stk.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXTINF&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;https&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;//mgtv-com.ycshengwang.com/20210519/fXE0kuJ7/1000kb/hls/FWZjOCHy.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#EXT&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;ENDLIST&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;字节解释&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EXT-X-VERSION：表示 HLS 协议版本号&lt;/li&gt;
&lt;li&gt;EXT-X-TARGETDURATION：表示 ts 视频片段允许最大的时长&lt;/li&gt;
&lt;li&gt;EXT-X-PLAYLIST-TYPE：表示流媒体类型&lt;/li&gt;
&lt;li&gt;EXT-X-MEDIA-SEQUENCE：表示播放列表第一个 ts 视频片段文件的序列号&lt;/li&gt;
&lt;li&gt;EXT-X-KEY：表示 ts 视频文件的加密信息
&lt;ul&gt;
&lt;li&gt;METHOD：加密方法，可选 &lt;code&gt;NONE&lt;/code&gt;、&lt;code&gt;AES-128&lt;/code&gt;、&lt;code&gt;SAMPLE-AES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;URI：密钥路径&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EXTINF：表示下面 url 对应的 ts 视频片段的时长&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>