<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Super Blog</title>
  
  <subtitle>循此苦旅 以达星辰</subtitle>
  <link href="https://superpung.com/atom.xml" rel="self"/>
  
  <link href="https://superpung.com/"/>
  <updated>2026-06-05T11:33:58.000Z</updated>
  <id>https://superpung.com/</id>
  
  <author>
    <name>SUPER</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  <follow_challenge>
    <feedId>65053891398556672</feedId>
    <userId>58518876444073984</userId>
  </follow_challenge>
  
  <entry>
    <title>[译] 我所知道的全部智能体工程技巧（2026 年 6 月）</title>
    <link href="https://superpung.com/agentic-engineering-2026/"/>
    <id>https://superpung.com/agentic-engineering-2026/</id>
    <published>2026-06-05T11:33:58.000Z</published>
    <updated>2026-06-05T11:33:58.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>作者：Matt Van Horn ｜ 发布于 2026 年 6 月 4 日</p><p>原文：<a href="https://www.linkedin.com/pulse/every-agentic-engineering-hack-i-know-june-2026-matt-van-horn-e2dkc">Every Agentic Engineering Hack I Know (June 2026)</a></p></blockquote><hr><p>三个月前我发过一篇《我所知道的全部 Claude Code 技巧》，拿到了 91.3 万阅读。当时 Kevin Rose 问我该用哪个 IDE，我的回答是：”不用 IDE，只用 <code>plan.md</code> 文件加语音。”</p><span id="more"></span><p>这种做法过去叫 <strong>vibe coding</strong>。大约在去年感恩节前后，模型强到让这个”玩具”变成了真家伙，也就是现在大家说的 <strong>Agentic Engineering（智能体工程）</strong>。它是我现在还能持续交付产品的唯一原因。今年我做出了 <a href="https://github.com/mvanhorn/last30days-skill">last30days</a>（2.7 万 star）、<a href="https://github.com/mvanhorn/cli-printing-press">Printing Press</a>（4000+ star）、刚上线的 <a href="https://github.com/mvanhorn/agentcookie">Agent Cookie</a>，还成了几个超大型开源项目的头部贡献者：Python、Go、GStack、Paperclip。上一次我做出别人觉得有价值的软件，还是高中时候的事。下面这些就是我的技巧。</p><blockquote><p><strong>YOLO 极速版技巧</strong>：把这整篇文章粘贴给你的 AI 智能体，让它据此做一份”把文中所有东西都搭起来”的计划，然后一条一条照着执行。这就是我的全套工作流，你连读都不用读。</p></blockquote><hr><h2 id="1-有想法的那一刻，就用-CE-做一个-plan-md"><a href="#1-有想法的那一刻，就用-CE-做一个-plan-md" class="headerlink" title="1. 有想法的那一刻，就用 CE 做一个 plan.md"></a>1. 有想法的那一刻，就用 CE 做一个 <code>plan.md</code></h2><p>依然是第一条铁律，也依然是我学到的最重要的一件事。</p><p>我一有想法，就立刻 <code>/ce-plan</code> 生成一个 <code>plan.md</code>【译注：<code>/ce-plan</code> 是下文 <a href="https://github.com/everyinc/compound-engineering-plugin">Compound Engineering</a> 插件提供的”斜杠命令”，作用是自动生成计划文件】。不是”让我想想”，也不是”先开始写代码”，而是每次都 <code>/ce-plan</code>。它还能吃图片，所以任何你能截下来的东西都能当起点：</p><ul><li>脑洞产品点子：<code>/ce-plan</code>。</li><li>GitHub 上的某个 bug：复制 issue 链接，粘贴，<code>/ce-plan</code>。</li><li>终端报错：Cmd+Shift+4 截图，Ctrl+V 粘贴，<code>/ce-plan fix this</code>（修一下这个）。</li><li>截图、报错信息、设计稿、Slack 对话串，随便哪个，丢进去就行。</li></ul><p>当想法还很模糊、我自己都不知道想要什么时，我会先用 <code>/ce-brainstorm</code> 跟智能体一起把它想清楚，等思路锐利了再 <code>/ce-plan</code>。</p><p><code>/ce-plan</code> 的内部机制是：并行地扇出多个研究型智能体。一个读你的代码库、找出其中的模式、核对你的代码规范；一个翻你过去的解决方案、提炼经验；如果话题需要，还会有更多智能体去查外部文档和最佳实践。全部同时进行。然后它汇总成果，写出一份结构化的 <code>plan.md</code>：问题出在哪、采用什么思路、要改哪些文件、带勾选框的验收标准、以及从你自己代码里总结出的可遵循模式。一切都扎根于你的仓库、你的规范、你的历史，而不是泛泛而谈的建议。</p><p><code>/ce-work</code> 则拿这份计划去落地。上下文爆了？新开一个会话，指向那份计划，从断点继续。计划就是那个能扛过一切的存档点。</p><p>传统开发是 80% 写代码、20% 做规划，这套打法把它反了过来：思考全压进计划里，执行就成了机械活。</p><p>让这一切真正跑起来的，是 Kieran Klaassen 和 Trevin Chow 的 <strong>Compound Engineering（复利工程）</strong> 插件【译注：”复利工程”取义于每次积累都让后续更省力，像利滚利】。我从它的铁粉变成了贡献者，如今是仅次于核心团队的第三大贡献者。我现在的规矩是：除非真的只是改一行字，否则永远先有一个 <code>plan.md</code>。</p><blockquote><p><strong>技巧</strong></p><ul><li>安装 Compound Engineering：<code>/plugin marketplace add EveryInc/compound-engineering-plugin</code></li><li>粘贴截图、bug 链接或报错，然后 <code>/ce-plan</code>，再 <code>/ce-work</code>。</li><li>想法模糊？先 <code>/ce-brainstorm</code>。</li></ul></blockquote><hr><h2 id="2-别去读那个-plan-md"><a href="#2-别去读那个-plan-md" class="headerlink" title="2. 别去读那个 plan.md"></a>2. 别去读那个 <code>plan.md</code></h2><p>我总是会做 <code>plan.md</code>，但几乎从不读它。计划是给智能体看的，傻乎乎的人类。</p><p>强制一份计划存在，能让智能体不偷懒：逼它去研究、敲定一个思路、写下验收标准，然后真的去逐条达成。有计划的编码智能体交付的是完整成品；没计划的会偷工减料、半途收手。计划就是那根牵狗绳。</p><p>所以我让它写计划，我只扫一眼标题，然后就跑 <code>/ce-work</code>。有疑问我就当场在会话里直接问：”等等，为什么用这个思路？”或者要个 TLDR。实在看不懂时就说”eli5 这个计划”【译注：eli5 &#x3D; “Explain Like I’m 5”，”像跟五岁小孩解释一样”，要求把复杂内容讲到极简】。我拿到一段话版本，点点头，继续往下走。我绝不会坐那儿读 300 行 Markdown，那是智能体的作业，不是我的。</p><p>做计划。信计划。别读计划。</p><blockquote><p><strong>技巧</strong>：管住自己别去读计划。要看就当场追问：<code>TLDR?</code>、<code>eli5 这个计划</code>，或者”等等，为什么用这个思路？”</p></blockquote><hr><h2 id="3-把-ce-plan-用在你最硬核的非工程工作上——给计划做个计划"><a href="#3-把-ce-plan-用在你最硬核的非工程工作上——给计划做个计划" class="headerlink" title="3. 把 /ce-plan 用在你最硬核的非工程工作上——给计划做个计划"></a>3. 把 <code>/ce-plan</code> 用在你最硬核的非工程工作上——给计划做个计划</h2><p>大家以为 <code>/ce-plan</code> 和 <code>/ce-work</code> 是写代码用的。我从三月以来学到的最大一件事是：它们不止于此。我现在最深度的知识工作都跑在同一个循环里，而诀窍是——让第一份计划成为”给计划做的计划”。这也不是我硬把一个写代码的工具掰去干别的：<code>/ce-plan</code> 内置了一个通用规划模式，本来就是为这种非代码工作设计的。</p><p>而且不只是商业问题。策略文档、产品规格书、竞品分析、董事会汇报，全都走同一个循环。</p><p>举个真实例子。我去见了 Michael Margolis——前 GV【译注：GV &#x3D; Google Ventures，谷歌旗下的风险投资基金】的研究合伙人，以”靶心客户法”闻名——聊我正在酝酿的一个商业难题。他让我读他的书，他网站上有免费 PDF。换作过去，我会大致翻翻就过去了。这次我打开 Claude Code，大意是这么说的：</p><blockquote><p>“<code>/ce-plan</code>，给计划做个计划。我马上要给你两样东西：Margolis 的书（PDF），以及我刚和他开的那场两小时会议的 Granola 转录全文【译注：Granola 是一款自动记录、转录会议的工具】，里面有我们讨论的全部上下文。我想要一份深思熟虑的计划，说明如何把我的商业难题、那场对话、和书里的洞见融合成我真正能用的东西。<strong>现在先别写那份文档</strong>。写出来才是真正的活。此刻我只要计划：你打算怎么读这本书、怎么从转录里挖料、怎么产出一份出色的文档。”</p></blockquote><p>接下来它花了 45 分钟，做出了一份史诗级的计划。</p><p>这也是我所知最能让大语言模型不偷懒的单一诀窍。直接叫它出成品，它会偷工减料；叫它先规划”我将如何产出这个成品”、再去执行那份规划，它每次都会给你下足功夫的深度版。</p><blockquote><p><strong>技巧</strong>：深度非代码工作——<code>/ce-plan</code> 给计划做个计划，把你所有上下文和转录都喂给它，然后 <code>/ce-work</code>。</p></blockquote><hr><h2 id="4-被”语音化”"><a href="#4-被”语音化”" class="headerlink" title="4. 被”语音化”"></a>4. 被”语音化”</h2><p>语音转 LLM 跟语音转其他任何东西都不一样。转录不必完美，因为听的那一方懂上下文，它会猜出麦克风听错的地方。你可以含糊、可以说一半停掉、可以一句话重说。语音之所以终于好用，是因为对面那东西聪明到能补上空缺。</p><p>我的配置：</p><ul><li><strong>Mac</strong>：Monologue（来自 Every）或 Wispr Flow，二选一，把语音直接灌进当前聚焦的任何 App，对着 Claude Code 说话就行。我还给办公室买了个鹅颈麦。</li><li><strong>手机</strong>：跳过 Monologue 和 Wispr Flow，在 iOS 上切换它们太烦。苹果自带的听写就够用了，因为你是在跟 LLM 说话，不是跟人。它把一半词都识别错，智能体照样能懂。潦草的笔记没关系。</li></ul><p>老实交代一点：我独处时用语音如鱼得水，在办公室就费劲。大家说你可以对着麦克风小声说，但我发现自己其实做不到，因为不想显得没礼貌、不想打扰旁边的人。所以合用办公室里的一张桌子，至今仍是我整套工作流的软肋。如果你能在开放式办公室里搞定语音又不变成”那种人”，请告诉我怎么做，我是真心求教。</p><blockquote><p><strong>技巧</strong>：Mac 装 Monologue 或 Wispr Flow；手机用苹果听写；搞一个鹅颈麦。</p></blockquote><hr><h2 id="5-在-cmux-里开一大堆标签页"><a href="#5-在-cmux-里开一大堆标签页" class="headerlink" title="5. 在 cmux 里开一大堆标签页"></a>5. 在 cmux 里开一大堆标签页</h2><p>这就是我真实的一天。四到六个 cmux【译注：<a href="https://github.com/manaflow-ai/cmux">cmux</a> 是一款终端会话管理&#x2F;多路复用工具，能在一个窗口里管理多个独立终端会话】标签页，有时更多，每个都是一个独立会话：</p><ul><li>一个在写计划。</li><li>一个在照另一份计划搭东西。</li><li>一个在跑 last30days。</li><li>一个在修我测上一件东西时发现的 bug。</li></ul><p>当 <code>/ce-plan</code> 在一个窗口里跑研究时，我切到另一个窗口去 <code>/ce-work</code> 一份已经写好的计划；趁那个在搭建，第三个窗口又贴进来一个新 bug。等我转一圈回来，第一个已经做完、在那儿等着了。</p><p>我听说 Orca 在移动端做得很棒。我以前也是 Ghostty【译注：Ghostty、Orca 都是终端模拟器软件】的纯粹主义者，但在 Ghostty 里我老是漏掉太多通知。</p><blockquote><p><strong>技巧</strong>：用 cmux（来自 Manaflow）；常开 4 到 6 个标签页，每个跑一件不同的任务。</p></blockquote><hr><h2 id="6-让终端默认打开就是-Claude-或-Codex，而不是-Shell"><a href="#6-让终端默认打开就是-Claude-或-Codex，而不是-Shell" class="headerlink" title="6. 让终端默认打开就是 Claude 或 Codex，而不是 Shell"></a>6. 让终端默认打开就是 Claude 或 Codex，而不是 Shell</h2><p>新标签页应该直接进入 Claude Code，而不是一个 Shell。开一个标签页，你就已经在跟智能体对话了。不用 <code>cd</code>，不用敲 <code>claude</code>。当开一个新会话只要一个按键时，你就会开很多很多个。我也不用文件夹，你的智能体自己能找到项目。</p><blockquote><p><strong>技巧</strong>：把下面这段粘给你的智能体：<br>“让每个新终端标签页都直接打开 Claude Code。在 <code>~/.config/ghostty/config</code> 里加一行 <code>command = ~/.local/bin/claude-launcher.sh</code>，不要动该文件里已有的任何其他设置。然后创建 <code>~/.local/bin/claude-launcher.sh</code>，让它运行 <code>claude --dangerously-skip-permissions</code>，并在 Claude 退出后打印一句简短提示、把我丢进一个交互式登录 zsh。给脚本 <code>chmod +x</code>。这对 Ghostty 和 cmux 都管用，因为 cmux 读的是同一份 Ghostty 配置。”</p></blockquote><hr><h2 id="7-远程控制每一个窗口，并给-Claude-Code-或-Codex-配个邮箱"><a href="#7-远程控制每一个窗口，并给-Claude-Code-或-Codex-配个邮箱" class="headerlink" title="7. 远程控制每一个窗口，并给 Claude Code 或 Codex 配个邮箱"></a>7. 远程控制每一个窗口，并给 Claude Code 或 Codex 配个邮箱</h2><p>两个让每个会话从任何地方都能触达的技巧。</p><p><strong>每次开新窗口都打开远程控制</strong>：把远程控制设成对每个会话自动开启。这样每个窗口都能从 Claude 手机 App 触达。在桌前开个会话，起身走人，在手机上接着同一个正在跑的任务继续。某种意义上，你是在遥控你家 Mac 上正埋头干活的东西。</p><p><strong>给你的 Claude 配个邮箱</strong>：Claude Code 可以借助 AgentMail 拥有一个邮箱地址【译注：AgentMail 是专门给 AI 智能体用的邮箱服务】。创始人 Adi Singh 教我的。给那个收件箱发邮件，一个全新会话就会打开，并开始处理邮件主题和正文里的内容，附件也能按路径调用。晚饭时发现个 bug？用手机把它发过去，你还没回到电脑前，一个会话就已经在跑了。我把整套东西开源了：<a href="https://github.com/mvanhorn/agentmail-to-claude-code"><code>github.com/mvanhorn/agentmail-to-claude-code</code></a>。</p><p>三个部件：</p><ol><li>一个守护进程通过 WebSocket【译注：一种保持长连接、能实时收推送的网络协议】盯着一个 AgentMail（YC S25 项目【译注：YC &#x3D; Y Combinator，知名创业孵化器；S25 指 2025 年夏季批次】）收件箱。每收到一封在白名单上的邮件，它就开一个全新 Claude 会话，把邮件写进一个提示词文件，让 Claude 读取并据此行动。</li><li>两个终端后端，cmux 或独立的 Ghostty，让它能驱动你本来就在用的那个。</li><li>一个发送器。我把它接到了我 Hermes 里的一个 <code>cc</code> 命令上，于是在手机上跑 <code>cc &lt;任务&gt;</code>，它就会作为一个正在干活的会话落到我的 Mac 上，不用 VPN，不用 SSH。</li></ol><p>白名单是那道闸门。只有你自己掌控的地址能进，任何过不了 DKIM 或 SPF【译注：DKIM、SPF 是验证邮件”真的来自所声称发件人”的反伪造机制】的邮件，在会话开启前就被丢弃。</p><blockquote><p><strong>技巧</strong></p><ul><li>常开远程控制：在 <code>~/.claude/settings.json</code> 里加 <code>&quot;remoteControlAtStartup&quot;: true</code>。</li><li>给 Claude 配邮箱：把这段粘给你的智能体——“用 <code>github.com/mvanhorn/agentmail-to-claude-code</code> 给 Claude Code 配个邮箱。克隆它，建一个 AgentMail 收件箱，在 <code>cc.env</code> 里填我的 API key、收件箱、只含我自己地址的白名单、以及我的终端（cmux 或 Ghostty），然后跑守护进程并把它装成一个 launchd 任务【译注：launchd 是 macOS 管理后台常驻任务的系统服务】。当我给那个收件箱发邮件时，这台 Mac 上应该打开一个全新 Claude Code 会话，开始处理主题和正文。”</li></ul></blockquote><hr><h2 id="8-危险地跳过权限确认——对，我是认真的"><a href="#8-危险地跳过权限确认——对，我是认真的" class="headerlink" title="8. 危险地跳过权限确认——对，我是认真的"></a>8. 危险地跳过权限确认——对，我是认真的</h2><p>Claude Code 每次编辑、每条命令都要请示许可。开着六个会话时你没法盯着它。两个设置能让它变得可用。有人说”auto”模式是更”安全”的做法，但它把我拖得太慢。</p><p><code>skipDangerousModePermissionPrompt: true</code> 是关键。没有它，Claude 每个会话都要你确认一遍。你也可以用 Shift+Tab 来回切换。有人跟我说较新的”auto”模式能在更安全的前提下达到差不多效果。也许吧。我选 YOLO。这是我自己的电脑。真把一切搞砸了，还有 GitHub 兜底【译注：指代码都提交到了 GitHub，出事能回滚】。我给一个朋友配 Claude Code 时，那 AI 还主动劝他别开这个模式。你得对它态度强硬点。</p><p>另一个设置是声音钩子【译注：hook，”钩子”，指在某事件发生时自动触发的小程序】，开六个会话时它不可或缺：</p><p>走开，听到声音再回来。六个会话都在跑时，靠声音你才知道是哪个刚做完。</p><blockquote><p><strong>技巧</strong><br>粘进 <code>~/.claude/settings.json</code>：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;allow&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span> <span class="string">&quot;WebSearch&quot;</span><span class="punctuation">,</span> <span class="string">&quot;WebFetch&quot;</span><span class="punctuation">,</span> <span class="string">&quot;Bash&quot;</span><span class="punctuation">,</span> <span class="string">&quot;Read&quot;</span><span class="punctuation">,</span> <span class="string">&quot;Write&quot;</span><span class="punctuation">,</span> <span class="string">&quot;Edit&quot;</span><span class="punctuation">,</span> <span class="string">&quot;Glob&quot;</span><span class="punctuation">,</span> <span class="string">&quot;Grep&quot;</span><span class="punctuation">,</span> <span class="string">&quot;Task&quot;</span><span class="punctuation">,</span> <span class="string">&quot;TodoWrite&quot;</span> <span class="punctuation">]</span><span class="punctuation">,</span> <span class="attr">&quot;deny&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="attr">&quot;defaultMode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bypassPermissions&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="attr">&quot;skipDangerousModePermissionPrompt&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span> <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;Stop&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;command&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;afplay /System/Library/Sounds/Blow.aiff&quot;</span> <span class="punctuation">&#125;</span> <span class="punctuation">]</span> <span class="punctuation">&#125;</span> <span class="punctuation">]</span> <span class="punctuation">&#125;</span> <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>【译注：<code>afplay</code> 是 macOS 播放音频的命令，这里在会话停止时播一声提示音】</p><p>Codex 有同样的 YOLO 模式。在 <code>~/.codex/config.toml</code>：</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">approval_policy</span> = <span class="string">&quot;never&quot;</span></span><br><span class="line"><span class="attr">sandbox_mode</span> = <span class="string">&quot;danger-full-access&quot;</span></span><br></pre></td></tr></table></figure><p>或者用 <code>codex --yolo</code> 临时起一个。</p></blockquote><hr><h2 id="9-我如何让大部分代码都跑在-Codex-上，却从不打开-Codex-CLI"><a href="#9-我如何让大部分代码都跑在-Codex-上，却从不打开-Codex-CLI" class="headerlink" title="9. 我如何让大部分代码都跑在 Codex 上，却从不打开 Codex CLI"></a>9. 我如何让大部分代码都跑在 Codex 上，却从不打开 Codex CLI</h2><p>我一整天都在把活派给 Codex，却几乎从不为此打开 Codex 的 CLI。Claude 做规划，Codex 来搭建，而我从不离开我的 Claude 会话。</p><p>不离开 Claude 就把活交给 Codex 的三种方式：</p><ul><li><strong>Codex IDE 扩展</strong>：派一个任务，应用结果，从不掉进 Codex 终端里。</li><li><code>/ce-work --codex</code>：在 Compound Engineering 循环里把搭建直接委托给 Codex。</li><li><strong>Printing Press 的 Codex 模式</strong>：在生成新 CLI 的提示词末尾加上 <code>codex</code>，它就把搭建交给 Codex。</li></ul><p>我的设置，两个引擎都拉满到超高推理强度：</p><ul><li><strong>Codex</strong>：推理 xhigh，快速模式常开。</li><li><strong>Claude Code</strong>：推理 xhigh，快速模式关闭。它的快速模式会在你那 200 美元的 Max 套餐之外按 token 另收费，所以我跳过。</li></ul><p>两个各 200 美元的套餐并排开，等于一整套第二引擎。我把大规模并行搭建推给 Codex，让 Claude 专注规划和品味。有些朋友反着用：Codex 搭建，Claude 审查。</p><blockquote><p><strong>技巧</strong></p><ul><li>Codex：推理 xhigh，快速模式开。Claude Code：xhigh，快速模式关。</li><li>把活交给 Codex：用 Codex IDE 扩展、<code>/ce-work --codex</code>，或在 Printing Press 提示词末尾加 <code>codex</code>。</li></ul></blockquote><hr><h2 id="10-规划之前先做研究：last30days"><a href="#10-规划之前先做研究：last30days" class="headerlink" title="10. 规划之前先做研究：last30days"></a>10. 规划之前先做研究：last30days</h2><p>在 <code>/ce-plan</code> 之前，我通常会先对它跑一遍 <code>/last30days</code>。</p><p>我当时在 Vercel 的 agent-browser 和 Playwright【译注：两者都是让程序自动操作网页&#x2F;浏览器的工具】之间纠结。我没去读文档，而是跑了 <code>/last30days Vercel agent browser vs Playwright</code>。几分钟内：几十条 Reddit 帖、X 帖、YouTube 视频、HN【译注：HN &#x3D; Hacker News，技术圈高人气的新闻社区】话题。结论是 agent-browser 每次调用占用的上下文少得多，Playwright 光是工具定义就要灌进去几千个 token。我把整份输出喂进 <code>/ce-plan integrate agent-browser</code>。出来的计划扎根于社区此刻真正掌握的认知，而不是半年前的训练数据。</p><p>last30days 是开源项目，现在已超过 2.6 万 star。它并行搜索 Reddit、X、YouTube、TikTok、Instagram、HN、Polymarket【译注：Polymarket 是一个预测市场平台，用下注价格反映大众对未来事件的判断】、GitHub 以及全网。我会在挑库之前、做功能之前、见商业伙伴之前、写文章之前都跑一遍。本文里有几样东西我就跑过它。研究、规划、搭建，这才是真正的循环。</p><blockquote><p><strong>技巧</strong>：安装 last30days（@slashlast30days）。在 <code>/ce-plan</code> 之前，跑 <code>/last30days &lt;主题&gt;</code>。记得配好 ScrapeCreators 的 key【译注：ScrapeCreators 是抓取社交平台内容的第三方接口，需要 API 密钥】。</p></blockquote><hr><h2 id="11-万物皆-Granola，并把原始转录丢进你的-LLM"><a href="#11-万物皆-Granola，并把原始转录丢进你的-LLM" class="headerlink" title="11. 万物皆 Granola，并把原始转录丢进你的 LLM"></a>11. 万物皆 Granola，并把原始转录丢进你的 LLM</h2><p>我和一位候选人吃了顿午饭，聊产品、聊吃的、聊孩子，九十分钟的正常闲聊，中间穿插着一个产品点子。</p><p><a href="https://www.granola.ai/">Granola</a> 一直开着。事后我把完整的原始转录粘进 Claude Code：<code>/ce-plan 把这个变成一份产品提案</code>。</p><p>诀窍在于<strong>原始</strong>。我不先做摘要，把整段乱糟糟的转录原样丢进去——连那些关于寿司的跑题也一起——让 Claude 对着我真实的代码库和我写过的每一份历史策略计划去做提取。Granola 的上下文 + 代码库 + 历史计划 &#x3D; 金子。它一次就产出了一份提案【译注：原文 one-shotted，指 AI 一遍到位、无需返工】，自动忽略了餐厅闲聊，我当晚就发了出去。那位现在全职和我们一起干。</p><p>而三月以来的升级是：Printing Press 的 Granola CLI，简直魔法。我能把任何一场会议作为干净的结构化数据直接拉进会话，搜遍我开过的每一场会，找出三周前某人说过的那一句话，再把它接进一份计划。再也不用复制粘贴。每一场会议的上下文，都只差一条命令。</p><blockquote><p><strong>技巧</strong>：把 Granola（@meetgranola）的<strong>原始</strong>转录丢进 <code>/ce-plan</code>，别先做摘要。安装 Printing Press 的 Granola CLI。</p></blockquote><hr><h2 id="12-人类信号"><a href="#12-人类信号" class="headerlink" title="12. 人类信号"></a>12. 人类信号</h2><p>这是我花了最久才完成的心态转变。当你同时跑六个智能体时，你的工作不是去干活，而是去当那个<strong>信号</strong>。</p><p>智能体提供产量，你提供品味、方向，以及”看一眼—再纠偏”的循环。你看它交回什么，你说”方案二更接近，但用方案一的措辞”、”先解决最大的风险”、”这段太长了”，它们就动起来。这个循环里稀缺而宝贵的东西，是你的判断力，不是你的打字。我越是投入去当那个”人类信号”、越是不再试图同时也当那只亲手干活的手，我交付得就越多。</p><p>去当品味，让它们当手。</p><blockquote><p><strong>技巧</strong>：用你的脑子去指挥你的智能体，以此给世界创造价值。这件事仍然有价值。</p></blockquote><hr><h2 id="13-用-HyperFrames-做视频，做任何东西"><a href="#13-用-HyperFrames-做视频，做任何东西" class="headerlink" title="13. 用 HyperFrames 做视频，做任何东西"></a>13. 用 HyperFrames 做视频，做任何东西</h2><p>视频曾经是我要么外包、要么干脆跳过的东西。现在我用做其他一切的同样方式做它：我说，智能体搭，我反馈。</p><p>HyperFrames 让我把视频当成 HTML 来做，所以智能体能写它【译注：<a href="https://github.com/heygen-com/hyperframes">HyperFrames</a> 是一种把视频用 HTML&#x2F;代码方式生成的工具】。循环和写代码一模一样，只不过产出是一个 MP4 而不是一个 PR。每个视频是一个文件夹，里面有逐场景的 <code>script.md</code>，动态文字排版【译注：kinetic typography，让文字带动画地出现&#x2F;运动的设计手法】，每个节拍都靠字幕承载。智能体把那份脚本变成画面合成并渲染出来。没有剪辑软件，没有时间轴。</p><p>我用这种方式做的上线短片：Granola CLI 演示、Agent Cookie 上线片。</p><p>做一个视频的成本降到了一次对话，于是任何值得配视频的东西现在都配上了：上线短片、产品演示、动画讲解、带字幕的片段。它们也不只发 X：我会把渲染好的演示直接丢进一个 PR，比如我给 atlas-lean（Facebook 的 AI 研究项目）提的那一个。</p><blockquote><p><strong>技巧</strong></p><ul><li>在 HyperFrames 里做视频：写一个 <code>script.md</code>，让你的智能体渲染成 MP4。</li><li>把 GIF 上传到 catbox【译注：<a href="https://catbox.moe/">catbox</a> 是一个免费的文件&#x2F;图片托管站】，它们在 GitHub、PR、README 和 issue 里都能漂亮地显示。</li></ul></blockquote><hr><h2 id="14-你的笔记就是你的智能体的知识库"><a href="#14-你的笔记就是你的智能体的知识库" class="headerlink" title="14. 你的笔记就是你的智能体的知识库"></a>14. 你的笔记就是你的智能体的知识库</h2><p>三月那个”策略文件夹”技巧被推广了。计划之所以一次比一次好，是因为 Claude 能访问我写过的每一份历史计划——上下文在复利。于是我把它指向了我的整个大脑。</p><p>我让它对接的工具：</p><ul><li><a href="https://bear.app/"><strong>Bear</strong></a>，配 Bear CLI。十年的笔记、会议、半成品点子和决定，一个智能体都能读能写。这就是个人版 RAG，只是不叫这个名字而已。我放进去的越多，每个会话就越聪明。</li><li><a href="https://obsidian.md/"><strong>Obsidian</strong></a>。我自己不用，但很多人爱用它干这个，而且它的插件生态很深。</li><li><a href="https://github.com/garrytan/gbrain"><strong>gbrain</strong></a>。我跨机器、跨智能体同步的”大脑”。</li><li><a href="https://github.com/supermemoryai/supermemory"><strong>supermemory</strong></a>。一个面向智能体的记忆层，很多人极力推荐。我正在深入试用，结论待出。</li></ul><p>这个技巧的形态本身才是重点：挑一个带 CLI 或 API 的笔记工具，把你的智能体指向它，让你自己的知识产生复利。</p><blockquote><p><strong>技巧</strong>：让你的智能体同时对接两类：你亲手写的记事工具（Bear、Obsidian），以及替你记忆的智能体大脑（gbrain，作者 @garrytan；supermemory，@supermemory）。挑带 CLI 或 API 的，这样它才能读取。</p></blockquote><hr><h2 id="15-随处办公——我的-Mac-mini"><a href="#15-随处办公——我的-Mac-mini" class="headerlink" title="15. 随处办公——我的 Mac mini"></a>15. 随处办公——我的 Mac mini</h2><blockquote><p><strong>技巧</strong></p><ul><li><strong>Mosh</strong>，当你不得不 SSH 登入时用它。它能在糟糕的 wifi 和漫游切换中让会话感觉仍像在本地、保持响应灵敏【译注：<a href="https://github.com/mobile-shell/mosh">Mosh</a> 是一种比传统 SSH 更耐网络抖动的远程登录工具】。用普通 SSH 时，Claude Code 会卡成蜗牛，每一次敲键都要等一个往返。在远程机器上，这就是”能用”和”痛苦”之间的差别。</li><li><strong>Tmux</strong>，坐飞机时用【译注：<a href="https://github.com/tmux/tmux">Tmux</a> 是终端会话管理器，能让程序在远程机器上持续运行、断线后重新接回】。在 tmux 会话里 SSH 进你的远程机器，活就跑在那台机器上，而不是你的笔记本上。飞越大西洋时 wifi 断了二十分钟，重连、attach（接回），它就停在你离开时的原处。我曾在从欧洲回家的整段航程里交付功能。</li><li><a href="https://github.com/NousResearch/hermes-agent">Hermes</a></li><li><a href="https://github.com/mvanhorn/agentcookie">Agent Cookie</a></li></ul></blockquote><hr><h2 id="16-Proof：把计划发给同事"><a href="#16-Proof：把计划发给同事" class="headerlink" title="16. Proof：把计划发给同事"></a>16. Proof：把计划发给同事</h2><p>一个 <code>plan.md</code> 对我来说完美，但要递给一个不住在终端里的人就毫无用处。这是最后一个真正的缺口，而 <a href="https://github.com/EveryInc/proof-sdk">Proof</a>（同样来自 Every Inc.）把它补上了。</p><p>在 Proof 里打开一份计划、像读文档一样读它，是不错。但它变得不可或缺，是在”把计划发给同事”这件事上。我把一个 <code>plan.md</code> 或一份规格书丢进 Proof，发出链接，一个不碰终端的普通人就能干净地读它、逐行评论，而那些评论又会回流进我和智能体的循环里。再也不用把 Markdown 粘进 Slack、眼看它渲染成一坨乱码。这是给整个”计划文件工作流”加上的”人在回路”审查【译注：human-in-the-loop，”人在回路”，指在自动化流程里保留人来把关、审核的环节】，也是头一回，把智能体工作分享给一个普通同事不再让人觉得别扭。</p><p>写这篇文章时我就把它装进了 Proof，它就是这么被审阅的。而我整篇文章是在 cmux 里写的，旁边就并排开着 Proof 审阅。</p><blockquote><p><strong>技巧</strong>：分享计划——把 <code>.md</code> 丢进 Proof（@EveryInc），发出链接，把评论拉回循环里。</p></blockquote><hr><h2 id="17-自己写-Skills（技能）"><a href="#17-自己写-Skills（技能）" class="headerlink" title="17. 自己写 Skills（技能）"></a>17. 自己写 Skills（技能）</h2><p>最大的升级不是用智能体，而是教会它们能记住的招式。任何我做超过两次的事，我都把它变成一个 <strong>skill</strong>：一个我的智能体可以永久调用的可复用命令【译注：skill 在此指给 AI 智能体写的、可重复调用的自定义命令&#x2F;能力包】。要自动化你的工作流，先从写你自己的 skill 开始。</p><p>你不用从零写。让我打通这件事的诀窍是：把你的智能体指向一个<strong>已经能用</strong>的 skill，让它照着那个形态仿一个。原话就是：”看看 Compound Engineering 那个 skill，帮我照它做一个用来自动化〔我要自动化的某事〕的。”它读一个优秀范例，学会结构，给我搭好脚手架【译注：scaffold，”脚手架”，指自动生成项目的基础框架代码，后续往里填内容】。我用这种方式攒了一大堆 skill。</p><p>这现在也是我开源生活的主要部分。看我的 GitHub，工作内容就是各种 skill 和围绕它们的工具。last30days 一开始就是我想给自己用的一个 skill，如今开源、超过 2.6 万 star。Printing Press 是一整座”生成 agent 原生 CLI”的工厂，是我个人用得最多的工具，被合并进它的 PR 超过 320 个。我是 Compound Engineering 本身的头部贡献者之一。这些都不是什么宏大计划。每一块都是我跑得足够频繁、值得让智能体在这件事上永久变强的工作流。</p><p>skill 写一次，之后每个会话都更快。这就是 Compound Engineering 里”复利”的那部分。</p><blockquote><p><strong>技巧</strong>：任何你做超过两次的事，做成一个 skill：”看看 Compound Engineering 那个 skill，帮我照它做一个用于〔X〕的。”</p></blockquote><hr><h2 id="18-开源：为你热爱的项目做贡献"><a href="#18-开源：为你热爱的项目做贡献" class="headerlink" title="18. 开源：为你热爱的项目做贡献"></a>18. 开源：为你热爱的项目做贡献</h2><p>让我自己的项目得以交付的同一个循环，也能交付别人的项目。我有数百个 PR 被合并进开源，包括 Python、Go、OpenCV、Vercel 的 Agent Browser、OpenClaw。不是顺手改个错别字那种，而是我每天都在用的工具上的真实功能。</p><p>不知不觉间，我开始登上贡献者榜单的前列：</p><ul><li>Compound Engineering、Superpowers、Emdash 上排第 3</li><li>GStack 和 Paperclip 上排第 4</li><li>Vercel 的 Agent Browser 上排第 6</li><li>Camoufox 上排第 2</li></ul><p>Pejman Pour-Moezzi 开玩笑说，每次他打开一个仓库，在贡献者头像墙里找我的脸，已经成了他个人版的”威利在哪里”【译注：”Where’s Waldo”，《威利在哪里》，一套在密集人群图里找特定人物的找人游戏】。</p><p>但被合并的 PR 并不是真正的奖赏，<strong>人</strong>才是。我跳进项目的 Discord，结识维护者，交到真朋友。这对招人也帮助巨大，我刚为我的新公司招到一位就是这样认识的工程师。你为热爱的东西做贡献，认识那些同样热爱它的人，然后它就复利滚动起来。</p><blockquote><p><strong>技巧</strong>：挑一个你每天都用的工具，找出它真正缺的一样东西，用同样的 <code>/ce-plan</code> + <code>/ce-work</code> 循环把它做出来。出现在项目的 Discord 里。PR 让你进门，而人，才是你留下来的原因。</p></blockquote><p><strong>在 X 上创造价值</strong>：在 X 上花每月 1–3 美元订阅你敬重的人。我每月给 @garrytan 付 1 美元，当我提交一个 PR 时，我能给他发一条 X 帖，他会收到一个”我是付费用户”的特别通知。我也付费订阅 @jason、@teknium。</p><hr><h2 id="19-我目前的笔记本配置"><a href="#19-我目前的笔记本配置" class="headerlink" title="19. 我目前的笔记本配置"></a>19. 我目前的笔记本配置</h2><p>我那台两年前的笔记本，在我塞给它的所有负载下——一整天六个 Claude 会话外加 Codex——几乎要罢工了。于是我升级到了 64GB 内存的 M5 Max。它是头猛兽，我很爱它。可它照样被这套负载折腾得够呛：我这台全新机器的电池有时只撑得了一小时。</p><p>所以我恐慌性地囤了电。我现在到哪都带一块 Anker 充电宝，还在特斯拉里常备一个 Anker 充电器，让车一路给我补电。</p><blockquote><p><strong>技巧</strong>：永不休眠——<code>sudo pmset -a disablesleep 1</code>【译注：pmset 是 macOS 的电源管理命令，这条意为禁用睡眠】。随身带一块 Anker 充电宝；车里放一个充电器。</p></blockquote><hr><h2 id="20-Printing-Press：能跑真实生活的-CLI"><a href="#20-Printing-Press：能跑真实生活的-CLI" class="headerlink" title="20. Printing Press：能跑真实生活的 CLI"></a>20. Printing Press：能跑真实生活的 CLI</h2><p>前面这些技巧大多活在终端里。这一个，是会走出终端的那个。</p><p>Printing Press 是一支 CLI 舰队，把现实世界的各种服务包起来，让一个智能体能直接去跑腿办事。它现在是自己独立的项目了（@ppressdev），star 超过 3700，我和 @trevin 一起在做。</p><p>让它们真正可用的那块拼图是鉴权，而它昨晚刚上线：<strong>Agent Cookie</strong>。它把你真实的浏览器会话交给一个 CLI，让它<strong>以你的身份</strong>行事，不用粘任何密码，也不用重新登录【译注：浏览器会话&#x2F;cookie 是你登录某网站后的”在线凭证”，把它交给程序，程序就能假装是已登录的你】。它就是把”一个知道某服务的智能体”变成”一个已经登录了该服务的智能体”的东西。</p><p>一个真实的下午，从头到尾：</p><ul><li><strong>特斯拉预热</strong>。十分钟后孩子要上车：”把车预热到 72 度。”特斯拉 CLI 触发，我们出门时车已经暖了。</li><li><strong>Instacart</strong>【译注：美国生鲜代买&#x2F;配送平台】。”按老样子再下一单，加上咖啡滤纸。”它把购物车建好，我确认，杂货就在路上。对话进行到一半顺手办的，节奏都没断。</li><li><strong>ESPN 轮询</strong>【译注：ESPN 是体育赛事平台；轮询指程序定时反复查询状态】。一个会话替我盯着一场比赛，只在比分接近时才提醒我。我什么都没刷，只收到了那一条真正重要的提醒。</li><li><strong>阿拉斯加航空</strong>，给孩子的旅行。它拉出票价和”肩部日期”【译注：shoulder dates，旺季与淡季之间、价格相对划算的出行日期】，查了我们的 Atmos 里程余额，喂进 <code>/ce-plan</code>，给出一套订票策略：最便宜的日期加上购买提醒。这些都是我在足球场边搞定的。</li></ul><p>不是”AI 替我写代码”。智能体工程会替你跑腿、替你看比赛、替你暖车、替你订行程，而你正在做别的事。</p><blockquote><p><strong>技巧</strong></p><ul><li>从 <code>printingpress.dev</code> 的库里装一个现成 CLI，把一件差事直接交给你的智能体。</li><li>无痛鉴权：Agent Cookie 把你真实的浏览器会话交给 CLI，让它以你的身份行事。</li><li>真正的技巧：印你自己的。挑一件你成天在做的事、一个你天天泡在里面的 API 或服务，让 Printing Press 给它生成一个 agent 原生的 CLI。你为自己工作流亲手造的那一个，才是真正改变你工作方式的那个。</li></ul></blockquote><hr><h2 id="21-实话部分：AI-精神病"><a href="#21-实话部分：AI-精神病" class="headerlink" title="21. 实话部分：AI 精神病"></a>21. 实话部分：AI 精神病</h2><p>智能体本该替我们干掉所有的活。结果，我认识的每一个朋友都在以这辈子最拼的状态工作。</p><p>容易脱口而出的回应是”歇歇吧，去外面走走”【译注：touch grass，英文网络流行语，字面”去摸摸草”，劝沉迷屏幕的人离开电脑去现实世界走走】。但这件事说的不是那个，说的是<strong>成瘾</strong>。用智能体搭东西，是有史以来最棒的电子游戏，而那个循环就是这么上头。</p><p>我有几个朋友我是真心担心。他们被”能造出任何东西”点燃，以至于别的什么都不干。然后他们上线了，没有用户。这没关系。我也上线过一堆没用户的东西。陷阱不在于那场空荡荡的上线，而在于消失在搭建里、弄丢了你身边的人。</p><p>所以小心点。和你爱的人说说话。问问自己，到底有没有人真的想要你在做的这个东西。如果诚实的答案是”它就是个给我自己用的工具”，那也没关系。我做过的一些最棒的东西，本来就只是给我自己的。</p><p>如果你确实想要受众，那就走 Gary Vaynerchuk【译注：知名营销人&#x2F;创业者，长期布道”持续做内容、慢慢积累受众”的路径】一直为内容布道的那条路。你从某个地方起步，对着虚空发帖，盼着有一个人注意到。然后三个、十个、一百个，你一路做到上千。没人一上来就是上千。你搭的任何东西也一样。</p><blockquote><p><strong>技巧</strong></p><ul><li>歇一歇。去外面走走。</li><li>和你爱的人说说话。</li><li>做人们真正想要的东西，哪怕”人们”只是你自己。</li></ul></blockquote><hr><h2 id="22-这篇文章就是这么写出来的"><a href="#22-这篇文章就是这么写出来的" class="headerlink" title="22. 这篇文章就是这么写出来的"></a>22. 这篇文章就是这么写出来的</h2><p>这是一个 Markdown 文件。Claude Code 跑在 cmux 里，我对着 <a href="https://www.monologue.to/">Monologue</a> 说话：”把’不用 IDE’那个开头进化一下”、”把’别读计划’那节写得更辣一点”、”加上特斯拉和 Instacart 的故事”。它重写，我反馈，然后它进 Proof 接受审阅。last30days 喂来新鲜素材。顺便说，这次没用 Zed【译注：<a href="https://github.com/zed-industries/zed">Zed</a> 是一款编辑器&#x2F;IDE】。我不再用它了。不用 IDE。不手打代码。说话、规划、搭建。从一张桌子、一张沙发、一辆车、一片足球场边。</p><p>这就是我在六月所知道的一切：一个语音 App、一个计划文件插件、几处配置改动、一堆标签页、一台 Mac Mini、两台远程机器，以及一支能跑真实生活的 CLI 舰队。</p><blockquote><p><strong>技巧</strong>：复制这整篇文章，粘进你的智能体，让它把能搭的都搭起来。好事会降临到你的智能体工程工作流上。</p></blockquote>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;作者：Matt Van Horn ｜ 发布于 2026 年 6 月 4 日&lt;/p&gt;
&lt;p&gt;原文：&lt;a href=&quot;https://www.linkedin.com/pulse/every-agentic-engineering-hack-i-know-june-2026-matt-van-horn-e2dkc&quot;&gt;Every Agentic Engineering Hack I Know (June 2026)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;三个月前我发过一篇《我所知道的全部 Claude Code 技巧》，拿到了 91.3 万阅读。当时 Kevin Rose 问我该用哪个 IDE，我的回答是：”不用 IDE，只用 &lt;code&gt;plan.md&lt;/code&gt; 文件加语音。”&lt;/p&gt;</summary>
    
    
    
    <category term="观点" scheme="https://superpung.com/view/"/>
    
    
  </entry>
  
  <entry>
    <title>2024：悲观者正确，乐观者前行</title>
    <link href="https://superpung.com/rev-2024/"/>
    <id>https://superpung.com/rev-2024/</id>
    <published>2025-02-04T17:06:29.000Z</published>
    <updated>2025-02-04T17:06:29.000Z</updated>
    
    <content type="html"><![CDATA[<p>回忆 2024 年的瞬间，在年的尺度上感受生命。</p><span id="more"></span><p>2024 年对我来说是很有意义的一年，虽然更加忙碌<del>（以至于本篇文章拖了一个多月才能写完）</del>，但好在还是做了很多事情。</p><h2 id="四季"><a href="#四季" class="headerlink" title="四季"></a>四季</h2><p>2024 开年伴随着春节，和多年未见的表哥见了面，但好朋友因事没能回家团聚。随着我们的成长与老去，越来越觉得如此团聚实属不易。谈论着儿时发生的故事，就像活在昨天一般。家庭是令人舒适的港湾，一年的紧张和辛劳都能在家人的陪伴中消散。</p><img data-src="https://61375c0.webp.li/2024-5km.JPG" alt="2024 第一个 5km" style="zoom:30%;" /><p>春天开始跑步，已经不记得上次跑 5km 是什么时候了。在 3 月 30 日完成了 2024 第一个 5km，跑步确实能让人感到快乐。</p><p><img data-src="https://61375c0.webp.li/2024-crabapple.jpg" alt="春日海棠"></p><p>四月正逢海棠花开，和女朋友一起去看花。满街的盛开和未盛开的海棠，映衬着熙熙攘攘的人流，在天空和古墙的背景下分外夺目。</p><p><img data-src="https://61375c0.webp.li/2024-IKEA.jpeg" alt="宜家"></p><p>趁着晴空万里，逛了宜家<del>（感觉就是库房 + 家具 + 餐厅）</del>。</p><p><img data-src="https://61375c0.webp.li/2024-platform.jpeg" alt="落日站台"></p><p>落日下的站台，伴随着春日的微风。铁路蜿蜒曲折，向落日延伸，是否能留住最后的余晖。</p><img data-src="https://61375c0.webp.li/2024-fastest-5km.jpg" alt="最快的 5km" style="zoom:30%;" /><p>夏天继续跑步，不断刷新 5km 记录。挑战体能，突破极限，正反馈令人备受鼓舞，在夏夜的晚风中可以忘掉一切。</p><p><img data-src="https://61375c0.webp.li/2024-summer-sunset.jpeg" alt="湖边落日"></p><p>在湖边欣赏落日，与宁静的湖水一起迎接夜幕降临。</p><p><img data-src="https://61375c0.webp.li/2024-fall.jpeg" alt="秋日"></p><p>转眼间到了秋天。自大学以来，一直只在寒假和暑假回家。今年为了弥补短暂的暑假，在十一假期赴上了回家的旅途。说来神奇，临时调整的会议安排、行程上的临时改签、得知我的发车时间临近而极限超车的出租车司机、差七分钟就要发车的高铁……一件件预料之外的突发情况仿佛在阻拦我回家的决定。</p><p>无论如何，我已经好久没有见过秋天的家乡了。这几年与家人团聚的时间日益减少，对家乡的印象也停留在极寒和极热之间。再会久违的家乡的秋高气爽，无比留恋。</p><p>时间来到了十一月，这是我一年中最难以忘怀的一段时间。在短暂的两周内，无数个 deadline 接踵而至。仍然有问题的实验代码、未完成的论文、未跑完的实验数据、答辩需要的材料、展示的 PPT、学术汇报的 PPT、学生的研究课题、学校的讲座安排、出差的时间规划、计划好和女朋友一起参与的活动……在那段时间，每一天的所有时段都安排好了任务。</p><p><img data-src="https://61375c0.webp.li/2024-work-in-taxi.JPG" alt="在出租车上工作"></p><p>甚至在出租车上，我也不得不在改文件。可惜改了一会就晕得不行，可能我不适合当这种打工人吧。</p><p>最难忘的是，两件重要的事仅隔 1 天。我需要同时准备好两场汇报，并在第一场汇报结束后乘飞机去往参会处，并在第二天参会之余准备好第三天的汇报。和我同住会议酒店的师兄在第三天也有汇报任务，于是在第二天晚上我们一起熬夜改 PPT……</p><p><img data-src="https://61375c0.webp.li/2024-rose.jpeg" alt="秋日玫瑰"></p><p>现在回想起来，那些事情都已有了阶段性的结果。好在挺过了那段紧张的时光，可惜错过了秋日的美景，只能在出差之余欣赏残存的玫瑰了。</p><h2 id="远方"><a href="#远方" class="headerlink" title="远方"></a>远方</h2><p>在忙碌之余，还好有时间出门走走。</p><p><img data-src="https://61375c0.webp.li/2024-CNBG.jpeg" alt="国家植物园"></p><p>春天，再次去了北京，到植物园看花。</p><p><img data-src="https://61375c0.webp.li/2024-shanghai-dalian.jpeg" alt="上海-大连"></p><p>晚春，在绿皮火车上，从黑夜到白天，沿着渤海湾，第一次踏足大连。</p><p><img data-src="https://61375c0.webp.li/2024-dalian.jpeg" alt="大连站"></p><p>大连的景色很美，人流量并不多，海边住宿的体验还不错。</p><p><img data-src="https://61375c0.webp.li/2024-dalian-square.jpg" alt="东港音乐喷泉广场"></p><p>在海边的广场，眺望着无边际的大海。没有人头攒动，只有海浪轻拂。</p><p>与大连相反，长沙是一个拥挤的城市。</p><p><img data-src="https://61375c0.webp.li/2024-huangxing-square.jpeg" alt="五一广场"></p><p>在夏天的长沙，五一广场上有无数的人来来往往。在这里见到了未曾见过的不夜城，即使到了凌晨也不曾退减。</p><p><img data-src="https://61375c0.webp.li/2024-hunan-museum.jpeg" alt="湖南省博物馆"></p><p>逛了湖南省博。</p><p><img data-src="https://61375c0.webp.li/2024-hunan-museum-mawangdui.jpeg" alt="马王堆"></p><p>以及马王堆。</p><p><img data-src="https://61375c0.webp.li/2024-orange-isle.jpeg" alt="橘子洲头"></p><p>还有橘子洲、岳麓山。</p><p>说来也巧，在我到达长沙前几天，这里正因大雨而关闭了岳麓山和橘子洲的参观。在到达后正逢开放参观，很高兴没有留下遗憾。</p><p><img data-src="https://61375c0.webp.li/2024-lobster.jpeg" alt="小龙虾"></p><p>长沙小龙虾确实好吃，十分推荐。</p><p><img data-src="https://61375c0.webp.li/2024-shaanxi-museum.jpeg" alt="陕西历史博物馆"></p><p>与长沙类似，西安的人流量也不小，但可以感受到其强大的历史底蕴。</p><p><img data-src="https://61375c0.webp.li/2024-xuanzang.jpeg" alt="玄奘像"></p><p>逛了陕历博、大雁塔、大唐不夜城。</p><p><img data-src="https://61375c0.webp.li/2024-roujiamo.jpeg" alt="肉夹馍"></p><p>西安肉夹馍确实好吃，十分推荐。</p><p>在西安的短暂几天，认识了许多新朋友，和大家玩得很开心。</p><h2 id="欢愉"><a href="#欢愉" class="headerlink" title="欢愉"></a>欢愉</h2><p>2024 年有许多快乐的瞬间，最难忘的是沉浸感受了天外来物。</p><p><img data-src="https://61375c0.webp.li/2024-extraterrestrial-1.jpeg" alt="天外来物"></p><p>从 16 年开始听他的歌曲，那是他还是“微博段子手”、“综艺狂”的人设。喜欢一首歌曲，有人因词共鸣，有人偏爱旋律，而我是前者。他的词曲陪伴了我 8 年，8 年间见证他从雪藏到闻名，从顶峰到低谷，从逆境到重生。有时很敬佩他的真诚，和对音乐的认真。</p><p><img data-src="https://61375c0.webp.li/2024-extraterrestrial-2.jpeg" alt="天外来物"></p><p>喜欢听他的歌，但也并不是“追星”，因此在此之前我甚至不知道“天外来物”巡回演唱会是何时开始的<del>（后来才发现 23 年来过我的城市，但当时没关注且没抢票……）</del>，最新的单曲也没有听过。偶然间得知他的巡演即将来到附近的城市，抱着试一试的心态竟然抢到了门票。</p><p><img data-src="https://61375c0.webp.li/2024-extraterrestrial-3.jpeg" alt="天外来物"></p><p>来到了演唱会的现场，在 23 年夏天看着录制的演唱会视频的我，如何能想到 24 年的今天能身临其境。震撼人心的开场、观众齐声的呐喊，无数脑海里的旋律如今在体育场回荡，身边是无数与我共鸣的人，台上是我多年倾听的“老友”。精心设计的演出桥段、极致的舞台表现力，带给我无数感动。当天的天气很冷，但在演出结束后他依然三次跪谢观众。</p><p>演唱会的体验带给我的是久久难忘的记忆，远胜于 8 年前偶然听到他的歌曲时的欣喜。在这之后，我开始重新了解他，重新聆听他的作品。才发现“天外来物”巡演从 21 年就已经开始，并已走遍了大江南北、世界东西。</p><p><img data-src="https://61375c0.webp.li/2024-xue-beijing.jpeg" alt="地铁站的薛之谦海报"></p><p>在收官之时，北京地铁可见演唱会海报，有“这城市怎么都是你”的感觉。在 818 苏州收官之后，又再次开启世界巡演返场，本巡也创造了许多历史性的“第一”。</p><p><img data-src="https://61375c0.webp.li/2024-extraterrestrial-store.jpeg" alt="“天外来物”便利店"></p><p>有趣的是，某天偶然在城市的角落发现了一个名为“天外来物”的便利店。</p><h2 id="重逢"><a href="#重逢" class="headerlink" title="重逢"></a>重逢</h2><p>很高兴在 2025 春节能与多年未见的老朋友重聚。</p><p><img data-src="https://61375c0.webp.li/2024-drone.jpg" alt="无人机"></p><p>一起在零下十几度的天气下飞无人机，差点没成功返航。</p><p><img data-src="https://61375c0.webp.li/2024-lantern.jpg" alt="孔明灯"></p><p>在冷风中尝试放飞孔明灯。</p><p><img data-src="https://61375c0.webp.li/2024-fireworks.jpeg" alt="烟花"></p><p><img data-src="https://61375c0.webp.li/2024-fireworks-gatling.jpg" alt="烟花"></p><p>除夕夜放烟花，仿佛回到了几年前。</p><p><img data-src="https://61375c0.webp.li/2024-lias-bar.jpeg" alt="Lias&#39; Bar"></p><p><img data-src="https://61375c0.webp.li/2024-pubg.jpeg" alt="PUBG"></p><p>一起玩了游戏。</p><p><img data-src="https://61375c0.webp.li/2024-dc1900.jpeg" alt="唐探 1900"></p><p>看了电影。</p><p><img data-src="https://61375c0.webp.li/2024-dinner.jpeg" alt="和朋友们聚餐"></p><p>和朋友们聚餐。</p><p><img data-src="https://61375c0.webp.li/2024-mahjong.jpeg" alt="麻将"></p><p>打麻将。</p><h2 id="数字"><a href="#数字" class="headerlink" title="数字"></a>数字</h2><p>仍然是数字总结时间。</p><p><img data-src="https://61375c0.webp.li/bilibili-2024.PNG" alt="Bilibili 2024"></p><p>341 天，比前几年略少了一些。</p><p><img data-src="https://61375c0.webp.li/netease-music-2024.PNG" alt="Netease Music 2024"></p><p>也许是因为用了 Apple Music，网易云的时间少了些。</p><p><img data-src="https://61375c0.webp.li/apple-music-2024.PNG" alt="Apple Music 2024"></p><p>周董的歌总是百听不厌。</p><p><img data-src="https://61375c0.webp.li/github-2024.png" alt="GitHub 2024"></p><p>可能比 23 年好一些？真的没有太多时间做开源了，写的大部分代码也都是论文的实验。</p><p><img data-src="https://61375c0.webp.li/gitlab-2024.png" alt="GitLab 2024"></p><p>实验代码的时间还是很多的。</p><p><img data-src="https://61375c0.webp.li/tju-expense-2024.png" alt="TJU Expense"></p><p>今年做了个校园卡支出的统计，Vibe Coding 很有趣。</p><p><img data-src="https://61375c0.webp.li/wakatime-2024.png" alt="Wakatime 2024"></p><p>Coding 的时间还是蛮多的。</p><p><img data-src="https://61375c0.webp.li/ns-2024.jpg" alt="Nintendo Switch 2024"></p><p>旷野之息和 Splatoon 是最喜欢的 NS 游戏。</p><p><img data-src="https://61375c0.webp.li/2024-footprint.jpg" alt="足迹"></p><p>今年去了几个城市，还不错！</p><h2 id="年度"><a href="#年度" class="headerlink" title="年度"></a>年度</h2><p>2024 年的年度 App 是：<a href="https://github.com/LazyVim/LazyVim">LazyVim</a>。</p><p>年度 App 提名是：</p><ul><li><a href="https://github.com/jesseduffield/lazygit">LazyGit</a></li><li><a href="https://timingapp.com/">Timing</a></li></ul><p>2024 年的年度影响力视频是：<a href="https://youtu.be/arj7oStGLkU">Inside the Mind of a Master Procrastinator | Tim Urban | TED</a></p><iframe width="560" height="315" src="https://www.youtube.com/embed/arj7oStGLkU?si=OYJsthvnJpJdtojl" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe><p>2024 年的年度综艺是：<a href="https://www.mgtv.com/h/648886.html">魔方新世界</a>。</p><p>今年看的电视剧和电影比较少，就不对年度剧集和年度电影进行评选了。</p><h2 id="Things-Learned-in-2024"><a href="#Things-Learned-in-2024" class="headerlink" title="Things Learned in 2024"></a>Things Learned in 2024</h2><ul><li>有时在年的尺度上感受生命，可以远离眼前的烦恼。试着把自己放在更大的时间尺度上看问题，或许能获得新的视角。</li><li>很多事情都是从顶层设计到底层实现，过分思考底层实现会牵制想象力，过分思考顶层设计会难以推进。</li><li>合作。即使有很多天才可以独立完成一些伟大的事情，但大部分人还是需要合作才能完成更大的目标。</li><li>多线程并行。以前往往会想把事情一件一件做好，但实际上很多事情是可以并行推进的。学会时间切片和任务切片，合理安排时间。</li><li>分配任务。在不同的事情中，自己扮演的角色往往不同。学会分配任务，才能合理利用资源。</li><li>多从他人角度思考，甚至是对立方。这样能更好地理解对方的需求和动机，明白一些事情究竟是为什么，从而找到更好的解决方案。</li><li>积极沟通，向上社交。社交本就是一个资源置换的过程，积极沟通能帮助少走很多弯路，也有助于提高思维的敏捷。</li><li>摆脱学生思维。和导师更多是合作关系，所以积极向导师提出问题并索取答案，好过被动等待导师指导。</li><li>不要害怕失败。</li><li>学会提出问题。</li><li>成功从来不是靠运气，只有脚踏实地。成功的故事常有，而成功者“愚蠢的尝试”不常听。</li><li>Critical thinking。大部分言论都是一家之言，不要先接受。</li><li>选择本就大于努力。没什么值得抱怨的，就像有人说的，悲观者正确，乐观者前行。</li></ul><h2 id="展望"><a href="#展望" class="headerlink" title="展望"></a>展望</h2><p>已经来临的 2025 年，希望能有时间多陪家人，多陪身边的人。在技术方面，希望能参与开源软件的贡献，完成一项 side project。保持健康，努力行动。</p><h2 id="往年"><a href="#往年" class="headerlink" title="往年"></a>往年</h2><ul><li><a href="/rev-2023">我和我的 2023</a></li><li><a href="/rev-2022">我和我的 2022</a></li><li><a href="/rev-2021">我和我的 2021</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;回忆 2024 年的瞬间，在年的尺度上感受生命。&lt;/p&gt;</summary>
    
    
    
    <category term="生活" scheme="https://superpung.com/life/"/>
    
    
  </entry>
  
  <entry>
    <title>Overleaf LaTeX citation 无法正常显示问题解决</title>
    <link href="https://superpung.com/overleaf-citation-error/"/>
    <id>https://superpung.com/overleaf-citation-error/</id>
    <published>2024-12-26T14:24:42.000Z</published>
    <updated>2024-12-26T14:24:42.000Z</updated>
    
    <content type="html"><![CDATA[<p>神奇的 Overleaf。</p><span id="more"></span><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>今天在用 Overleaf writing 的时候，使用 <code>\cite&#123;paper&#125;</code> 出现 <code>?</code> 而非 ref 序号。<code>main.tex</code> 的具体内容如下：</p><figure class="highlight latex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\usepackage</span>&#123;filecontents&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">\begin</span>&#123;filecontents&#125;&#123;<span class="keyword">\jobname</span>.bib&#125;<span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">@article&#123;you2023,</span></span><br><span class="line"><span class="string">  author =       &#123;You&#125;,</span></span><br><span class="line"><span class="string">  title =        &#123;Overleaf is bad&#125;,</span></span><br><span class="line"><span class="string">  publisher =    &#123;Science&#125;,</span></span><br><span class="line"><span class="string">  year =         2023</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">@article&#123;you2024,</span></span><br><span class="line"><span class="string">  author =       &#123;You&#125;,</span></span><br><span class="line"><span class="string">  title =        &#123;Overleaf is not what you need at all&#125;,</span></span><br><span class="line"><span class="string">  publisher =    &#123;Science&#125;,</span></span><br><span class="line"><span class="string">  year =         2024</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span><span class="keyword">\end</span>&#123;filecontents&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">\begin</span>&#123;document&#125;</span><br><span class="line"></span><br><span class="line">Overleaf is bad~<span class="keyword">\cite</span>&#123;you2023&#125;.</span><br><span class="line"></span><br><span class="line">Overleaf is not what you need at all~<span class="keyword">\cite</span>&#123;you2024&#125;.</span><br><span class="line"></span><br><span class="line">DO NOT use Overleaf~<span class="keyword">\cite</span>&#123;you2024,you2023&#125;.</span><br><span class="line"></span><br><span class="line"><span class="keyword">\bibliographystyle</span>&#123;plain&#125;</span><br><span class="line"><span class="keyword">\bibliography</span>&#123;<span class="keyword">\jobname</span>&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">\end</span>&#123;document&#125;</span><br></pre></td></tr></table></figure><p>显示效果为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Overleaf is bad [?].</span><br><span class="line">Overleaf is not what you need at all [?].</span><br><span class="line">DO NOT use Overleaf [?, ?].</span><br></pre></td></tr></table></figure><p>但十分奇怪的是，并不是开始时就出现此问题。当我新建项目并导入 <code>TeX</code> 文件时，Overleaf 可以正常编译并显示 ref 序号：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Overleaf is bad [1].</span><br><span class="line">Overleaf is not what you need at all [2].</span><br><span class="line">DO NOT use Overleaf [1, 2].</span><br></pre></td></tr></table></figure><p>当删除 ref 内容再重新粘贴回去时，序号就会变为 <code>?</code>，且重新编译不能解决。</p><p>于是搜索了一下相关问题，但并没有找到类似问题的讨论。尝试了清除缓存、检查并修改文件名、检查 <code>sty</code> 文件、调整文件路径、更换编译器等操作后，均无法解决此问题。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>Overleaf 内置的编译器（<code>pdfLaTeX</code> 和 <code>LaTeX</code>）对 <code>\jobname</code> 的支持有问题，因此需要将隐式的 <code>\jobname</code> 替换为显式的文件名称。</p><p>以 <code>main.tex</code> 举例，需要将 <code>\jobname</code> 替换为 <code>main</code>：</p><figure class="highlight latex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">\usepackage</span>&#123;filecontents&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">\begin</span>&#123;filecontents&#125;&#123;main.bib&#125;<span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">@article&#123;you2023,</span></span><br><span class="line"><span class="string">  author =       &#123;You&#125;,</span></span><br><span class="line"><span class="string">  title =        &#123;Overleaf is bad&#125;,</span></span><br><span class="line"><span class="string">  publisher =    &#123;Science&#125;,</span></span><br><span class="line"><span class="string">  year =         2023</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">@article&#123;you2024,</span></span><br><span class="line"><span class="string">  author =       &#123;You&#125;,</span></span><br><span class="line"><span class="string">  title =        &#123;Overleaf is not what you need at all&#125;,</span></span><br><span class="line"><span class="string">  publisher =    &#123;Science&#125;,</span></span><br><span class="line"><span class="string">  year =         2024</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span><span class="keyword">\end</span>&#123;filecontents&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">\begin</span>&#123;document&#125;</span><br><span class="line"></span><br><span class="line">Overleaf is bad~<span class="keyword">\cite</span>&#123;you2023&#125;.</span><br><span class="line"></span><br><span class="line">Overleaf is not what you need at all~<span class="keyword">\cite</span>&#123;you2024&#125;.</span><br><span class="line"></span><br><span class="line">DO NOT use Overleaf~<span class="keyword">\cite</span>&#123;you2024,you2023&#125;.</span><br><span class="line"></span><br><span class="line"><span class="keyword">\bibliographystyle</span>&#123;plain&#125;</span><br><span class="line"><span class="keyword">\bibliography</span>&#123;main&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">\end</span>&#123;document&#125;</span><br></pre></td></tr></table></figure><p>但如果只是这样替换，并无法解决问题。还需要在同目录下新建 <code>main.bib</code> 文件，将 <code>\begin&#123;filecontents&#125;&#123;main.bib&#125;</code> 和 <code>\end&#123;filecontents&#125;</code> 之间的内容拷贝至 <code>main.bib</code>。此时可以删除 <code>main.tex</code> 中的对应内容。</p><p>此时，重新编译即可解决问题：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Overleaf is bad [1].</span><br><span class="line">Overleaf is not what you need at all [2].</span><br><span class="line">DO NOT use Overleaf [1, 2].</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;神奇的 Overleaf。&lt;/p&gt;</summary>
    
    
    
    <category term="技术" scheme="https://superpung.com/tech/"/>
    
    
    <category term="LaTeX" scheme="https://superpung.com/tags/LaTeX/"/>
    
    <category term="Overleaf" scheme="https://superpung.com/tags/Overleaf/"/>
    
  </entry>
  
  <entry>
    <title>Mac 终端无法联网问题解决</title>
    <link href="https://superpung.com/mac-terminal-network/"/>
    <id>https://superpung.com/mac-terminal-network/</id>
    <published>2024-12-20T18:04:16.000Z</published>
    <updated>2024-12-20T18:04:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>神奇的 macOS。</p><span id="more"></span><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>今天本想装个东西，但 <code>brew update</code> 时一直报 <code>could not resolve host</code>。第一时间觉得是 Clash 的问题，于是设置了 proxy，但仍未解决，尝试 unset 但也无济于事。</p><p>后来发现不仅对于 <code>github.com</code> 报错，甚至对镜像 <code>mirrors.tuna.tsinghua.edu.cn</code> 也报错，<code>ping</code> 了一下 <code>github.com</code> 和 <code>baidu.com</code>，果然都不通。因此并不是 proxy 的问题。</p><p>尝试 <code>ping 8.8.8.8</code>，发现可以通。因此原因基本可以锁定是 DNS 设置的问题。</p><p>但之前从未更改过 Mac 的 DNS 设置，为什么终端突然不能联网了呢？尝试在 Settings - Wi-Fi - Details - DNS - DNS Servers 添加了两个阿里云的 DNS Server：<code>223.5.5.5</code>、<code>223.6.6.6</code>，并刷新 DNS 缓存：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dscacheutil -flushcache</span><br><span class="line"><span class="built_in">sudo</span> killall -HUP mDNSResponder</span><br></pre></td></tr></table></figure><p>稍等几分钟后，终端网络恢复正常。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>当 Mac 终端无法联网时，大多是因为两种情况：proxy 和 DNS，首先可以通过 <code>ping</code> 一个 IP（如 <code>ping 8.8.8.8</code>）检查是否是 DNS 的问题。</p><p>如果是 DNS 的问题，手动在 Settings - Wi-Fi - Details - DNS - DNS Servers 处添加公共 DNS Server（如 <code>223.5.5.5</code> 和 <code>223.6.6.6</code>）并刷新缓存：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dscacheutil -flushcache</span><br><span class="line"><span class="built_in">sudo</span> killall -HUP mDNSResponder</span><br></pre></td></tr></table></figure><p>这种方式在大多数情况下应该都可以解决 DNS 问题导致的网络连接问题。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;神奇的 macOS。&lt;/p&gt;</summary>
    
    
    
    <category term="技术" scheme="https://superpung.com/tech/"/>
    
    
    <category term="Mac" scheme="https://superpung.com/tags/Mac/"/>
    
    <category term="Apple" scheme="https://superpung.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>我和我的 2023</title>
    <link href="https://superpung.com/rev-2023/"/>
    <id>https://superpung.com/rev-2023/</id>
    <published>2023-12-31T03:58:24.000Z</published>
    <updated>2023-12-31T03:58:24.000Z</updated>
    
    <content type="html"><![CDATA[<p>上一次在学校跨年还是 2019 年。四年过去了，我还在这里。</p><span id="more"></span><p>如果有人问我，你和四年前有何不同？很遗憾，我并不确定自己是否真正做出了改变。在相似的新旧交替之际，我试着寻回四年前的记忆，却不敢认出那个自己。穿越时间的相遇，让我发现自己并没有变得更好，反而失去了那时一般的快乐，也失去了那时对今日的期望。我不知道是否“成长”皆如此般令人怅然，只知道行路至此，我依然没有踏出那片荒野。</p><p>2023 年真的如去年期望一般变好了吗？也许有些，但在不同的阶段总会有不同的困难。2023 对我来说是一个比较大的转折点，从越来越躺平的本科生活到忙碌的研究生生活，我逐渐明白了若想有所为则需有所不为。遇到的选择题从来不会减少，所以只能按照局部最优踟躇向前。站在 2024，与背后的 2019 已经相隔了五年，却仍感觉 2019 很近，也许是因为 2020-2022 并没有留下太多的记忆点，这段时间被我们在内心折叠。</p><p>我的 2023 也如此一般飞逝。翻看相册才发觉，虽然上半年自由的时间很多，但下半年的大部分时间都忙于工作，甚至没留下许多照片。无法挽回我这越来越无聊的生活，只希望前行的路上亦能且行且歌。</p><hr><p>现在回想起来，22 年冬天可能是我人生中最后一个美好的寒假。和家人团聚，和老友重聚，有许多时间自我冥想和放空。</p><p>通过了考试，准备复试，录取。这段时间很难熬，归根到底是对未知的恐惧和对未来的迷茫。只记得复试前的几天天气阴沉，复试之后开始放晴，我的内心也逐渐平静。总有许多话想记录，但零碎的空闲时间击碎了记录的热情。希望我能在下一个自己的时间完成这些事情……</p><p>上半年的空闲时间很多，重新逛了一次北京，感觉和七年前的印象好不一样。来一次说走就走的旅行，第一次踏上山东的土地，感谢 YF 的招待。看了很多风景，吃了很多美食，两次通宵 KTV 印象深刻。喜欢这种自由，希望 2024 依旧有心情行万里路。</p><p>毕业，一条分隔线将我的人生划开。也许是因为常常低头行路，却不曾发现自己已经走了很远。和朋友们一个一个道别，各自去往不同的方向，四年的喜怒哀乐在两本证书、一张照片中被封存。我知道这种经历不会再来，就像我的青春一去不复还。</p><p>分隔线开启了我忙碌的下半年。研究生生活虽然带来了更多自由的时间，但未曾涉足的领域和新的挑战也让我压力倍增。</p><blockquote><p>It is no use doing what you like; you have got to like what you do.</p></blockquote><p>导师和实验室的师兄都很 nice，大家都很有实力，希望我自己也能应对不断的挑战，继续走下去。</p><p>在少有的闲暇时间，突然对摄影很感兴趣，入手了 📷 。结局就是过于忙碌以至于吃灰了许久，虽然拍了一些照片，但还有好多没来得及修。希望有时间能多出去走走。</p><p>忙里得闲，压力需要娱乐缓解。重拾了 Steam 游戏，也突然对 NS 很感兴趣。遗憾的是 23 年的末尾才开始接触 NS，50 小时+ 畅游海拉鲁大陆没有让我感到疲倦。内心的紧张和压抑在游戏中得以释怀，希望 2024 能找回更多的乐趣。</p><hr><p>OK，结束这压抑的流水账，接下来是一年一度的数字总结时间！</p><p>首先是网易云音乐，从下到上依次是 2020、2021、2022、2023 年度总结。</p><p><img data-src="https://i0.hdslb.com/bfs/article/6c53e8c3413e9e8ec455af809733dc2e291637678.jpg@27p_100q.jpg" alt="netease-music-2023"></p><img data-src="https://i0.hdslb.com/bfs/album/18d23ec6e1d91a6d387a6452a11557f13f0fcfa4.png" alt="netease-music-2022" style="zoom:36%;" /><p><img data-src="https://i0.hdslb.com/bfs/album/dc3d493bc67161ae50101da2e6a81a5470b5fe72.png" alt="2021-22"></p><p><img data-src="https://i0.hdslb.com/bfs/album/94cef62384e8d5ec2029703d34ab92f7d59e63d6.png" alt="2021-24"></p><p>23 年用网易云的时间屈指可数，因为网易云音乐 app 冗余的功能、倒退的设计、少量的曲库、更多的广告，让我不得不放弃这个平台。去年曾开始使用 Apple Music，但每次 App Store 换区时我的音乐库都会被清空，所以我也不得不放弃它。最终选择了 Spotify，目前仍然是我的主要音乐平台。</p><p><img data-src="https://i0.hdslb.com/bfs/article/a5df50f6de94a6bdae2d75b81495e551291637678.jpg@30p_100q.jpg" alt="spotify-2023"></p><p>然后是 Bilibili，我以为 2023 我会比 2022 时间更长，但事实上看的更少了。</p><table><tr align=center><td><img data-src="https://i0.hdslb.com/bfs/album/ed49fe23ded6dd54d3149c7e11452f9b4c701f9b.png" alt="2021-23" height=500px /></td><td><img data-src="https://i0.hdslb.com/bfs/album/fe48890094cb5f6cf8af29766ee079bf37082d8f.jpg" alt="bilibili-2022" height=500px /></td><td><img data-src="https://i0.hdslb.com/bfs/article/57ade8c3baa9002660923941bd4f4451291637678.png" alt="bilibili-2023" height=500px /></td></tr></table><p>2023 依然没有在开源事业上有所进步。有几个 project 的 idea，但苦于没有时间实现，也有几个 project 写了一点就不了了之。希望 2024 我能写完一个。</p><p><img data-src="https://i0.hdslb.com/bfs/article/2724af94ae19bb56822a7009f545ad04291637678.png" alt="github-2023"></p><hr><p>从去年开始，我计划每年评选出 1～2 个“年度 app”。2021 年度 app 是 Notion，这也是我目前主力笔记软件；2022 年度 app 是 Arc，这也是我目前主力浏览器。2022 另一个年度 app 是 ChatGPT，没想到它在 2023 年引发了世界人工智能和大语言模型的热潮。2023 我评选的年度 app 是 1Password，它改变了我记录密码的方式，也让我更加安全地管理越来越多的账户。</p><p>除了“年度 app”，我还打算评选出我的“年度影响力视频”。今年的是 <a href="https://www.bilibili.com/video/BV19c411o7os">【毕导】13年过去了，我终于毕业啦！_哔哩哔哩_bilibili</a>。</p><iframe src="//player.bilibili.com/player.html?aid=280129629&bvid=BV19c411o7os&cid=1313429825&p=1&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><p>除了“年度影响力视频”，我还打算评选“年度电影”和“年度剧集”。今年一共看了 33 部电影和剧集，我的“年度电影”是 <a href="https://movie.douban.com/subject/1292343">《蝴蝶效应》</a>，“年度剧集”是 <a href="https://movie.douban.com/subject/35588177">《漫长的季节》</a>。</p><p>以上评选的内容都出于我的主观评价，对我来说都很有意义。期待 2024 可以看到更多优秀的作品。</p><hr><p>2023 带给我最大的感受就是，我开始更深刻地认识自身。</p><p>我不断认识到自己的缺点，这些在我独自完成有挑战性的任务时总会显现出来。</p><p>2023 是充满挑战的一年，我不在沉迷于自己的舒适圈，开始寻求一些改变。</p><p>痛苦常伴，快乐易逝；道阻且长，不虚此行。愿来年我能过得从容，至少改掉一个缺点，至少完成一个目标。</p><p>2024，要开心。</p><p><img data-src="https://i0.hdslb.com/bfs/article/fbbed187a98cd5598831b6c35e05899a291637678.jpg" alt="rev-2023"></p><p>2023，再见！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;上一次在学校跨年还是 2019 年。四年过去了，我还在这里。&lt;/p&gt;</summary>
    
    
    
    <category term="生活" scheme="https://superpung.com/life/"/>
    
    
  </entry>
  
  <entry>
    <title>新起点</title>
    <link href="https://superpung.com/new-ui/"/>
    <id>https://superpung.com/new-ui/</id>
    <published>2023-04-03T08:23:27.000Z</published>
    <updated>2023-04-03T08:23:27.000Z</updated>
    
    <content type="html"><![CDATA[<p>新的样子，新的开始。</p><span id="more"></span><p>忙碌了 5+3 个月，很多重要的事情终于有了结果。过去的一个月可能是比较 challenging 的一个月，面对种种未知和阻碍，最后还是挺了过来。很难说收获了什么经验，只不过是随着时间的流逝而不得不面对罢了。</p><p>话说回来，从寒假开始构思如何改造本站的 UI 以更符合我的审美，最终做了如下更改：</p><ol><li>不再使用英文为主要语言，改为完全中文站；</li><li>改用 <a href="https://github.com/lxgw/LxgwWenKai">霞鹜文楷</a> 作为主要字体；</li><li>重构许多 UI 细节，增加圆角等；</li><li>重构色彩搭配，重新设计了亮暗模式；</li><li>不再使用 <a href="https://github.com/utterance/utterances">utterances</a> 为评论系统，改为使用 <a href="https://github.com/giscus/giscus">giscus</a>，也就是把评论系统从 Issues 改为 Discussions；</li><li>其他若干修改。</li></ol><p>当然，这次对 NexT 主题的魔改也存在不合理之处，目前发现的 bug 有：</p><ol><li><del>评论需要手动刷新才加载</del> <a href="https://github.com/next-theme/hexo-next-giscus/commit/84531743bf8d86e12bb6f7950e81a5a135bc48c8">已解决</a>；</li><li>评论在暗色模式下仍呈现亮色；</li><li>字体的粗体格式很不明显；</li><li>其他若干问题，欢迎你在下方评论区提出。</li></ol><p><img data-src="https://article.biliimg.com/bfs/article/2dbc2aa46af9623a181a2e58f3edf4345dabdd89.png" alt="旧版UI"></p><p><img data-src="https://article.biliimg.com/bfs/article/b1a1c65cce9595a45dfc319311f1435409d1979c.png" alt="新版UI"></p><p>现在开始，是本站的新起点，也是我的新篇。</p><blockquote><p>最近应该会更新几篇文章，敬请期待（在写了在写了）。</p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;新的样子，新的开始。&lt;/p&gt;</summary>
    
    
    
    <category term="生活" scheme="https://superpung.com/life/"/>
    
    
    <category term="博客配置" scheme="https://superpung.com/tags/%E5%8D%9A%E5%AE%A2%E9%85%8D%E7%BD%AE/"/>
    
  </entry>
  
  <entry>
    <title>使用 Arc 浏览器自定义网页</title>
    <link href="https://superpung.com/arc-boost/"/>
    <id>https://superpung.com/arc-boost/</id>
    <published>2023-01-27T05:52:06.000Z</published>
    <updated>2023-01-27T05:52:06.000Z</updated>
    
    <content type="html"><![CDATA[<p>方便、快捷地自定义网页，无需安装任何插件。</p><span id="more"></span><h2 id="什么是-Arc-浏览器"><a href="#什么是-Arc-浏览器" class="headerlink" title="什么是 Arc 浏览器"></a>什么是 Arc 浏览器</h2><p>在上一篇文章 —— <a href="/rev-2022">2022 年度总结</a> 中，我提到了去年比较满意的一款产品：Arc 浏览器。它是一款基于 Chromium 的浏览器，有很多传统浏览器没有的新功能。这里不过多介绍它的其他功能，主要讲一讲自定义网页 —— 即 <a href="https://youtu.be/53KQ2wUZG2s">Arc Boost</a>。</p><h2 id="为什么要自定义网页"><a href="#为什么要自定义网页" class="headerlink" title="为什么要自定义网页"></a>为什么要自定义网页</h2><p>我喜欢使用浏览器，只要浏览器能做的，我一般不会下载 app。很多网站的布局、设计和推荐的内容都不是我想要的，我希望能够自定义网页，只看到我想要的内容。比如在用 Bilibili 时，视频上方的推荐搜索、直播间花里胡哨的活动通知和礼物等，我都不想看到。这些内容在 app 中是不可以自定义的，但正因为是浏览器，才有了自定义网页的可能。</p><h2 id="为什么不…？"><a href="#为什么不…？" class="headerlink" title="为什么不…？"></a>为什么不…？</h2><h3 id="为什么不直接修改网页源代码？"><a href="#为什么不直接修改网页源代码？" class="headerlink" title="为什么不直接修改网页源代码？"></a>为什么不直接修改网页源代码？</h3><p>这样做的话，每次网页刷新都需要重新修改，而且不同路由下的网页都需要修改，比较麻烦。</p><h3 id="为什么不直接安装插件？"><a href="#为什么不直接安装插件？" class="headerlink" title="为什么不直接安装插件？"></a>为什么不直接安装插件？</h3><div class="note info"><p>上文和下文提到的“插件”指的就是浏览器的扩展（extension）。</p></div><p>当然，对于 Bilibili 网站，有很多出色、强大的自定义插件可供使用。但插件提供了太多我不需要的功能，提供的需要的功能实现又过于复杂也不是我想要的样子，而且向插件中添加我想要的功能又太麻烦，最后成了为了自定义网页而去自定义插件。</p><h2 id="如何快速利用-Arc-Boost-自定义网页（以-Bilibili-为例）"><a href="#如何快速利用-Arc-Boost-自定义网页（以-Bilibili-为例）" class="headerlink" title="如何快速利用 Arc Boost 自定义网页（以 Bilibili 为例）"></a>如何快速利用 Arc Boost 自定义网页（以 Bilibili 为例）</h2><p>首先，你要有一台运行 macOS 的电脑，因为 Arc 浏览器暂时只支持 macOS。</p><p>其次，你需要安装 Arc 浏览器，这里是邀请码：</p><blockquote><p>hey, here’s an invite to Arc, the browser I was telling you about!</p><p><a href="https://arc.net/gift/44d83098">https://arc.net/gift/44d83098</a></p></blockquote><p>在浏览器中打开 <a href="https://bilibili.com/">Bilibili</a>，在侧边栏的右下角 <code>+</code> 号中选择 <code>New Boost</code>，即可进入 Arc Boost 编辑页面。</p><p><img data-src="https://i0.hdslb.com/bfs/album/1dbcab3fdcf8e8d848f326145da195165d14c18e.png" alt="Arc Boost 编辑页面"></p><p>点击 <code>Style</code> - <code>A specific website</code>，在窗口中将 <code>www.bilibili.com</code> 改为 <code>bilibili.com</code>，点击 <code>Create Boost</code>，即可进入 <code>styles.css</code> 编辑页面。</p><div class="note info"><p>因为 Bilibili 空间、直播间等页面的三级域名都不是 www，如果这里只写 www，会导致自定义 css 在这些页面无法生效。</p></div><p><img data-src="https://i0.hdslb.com/bfs/album/22e9ac3d7f36682460d2259855ce4983f5d0645f.png" alt="Arc Boost css 页面"></p><p>这里就是自定义网页的地方了，你可以在这里添加你想要针对 Bilibili 的 css 代码，浏览器会自动覆盖掉原网页的 css。</p><div class="note info"><p>你应该对 css 有一些比较基础的了解。如果完全不了解，Arc 浏览器还贴心地提供了很多教程，在右上角的 Handbook 中可以找到。</p></div><p>下面以隐藏 Bilibili 网页上方的推荐搜索为例，这里是修改前的网页：</p><p><img data-src="https://i0.hdslb.com/bfs/album/3bf9db6c793b12ddbfb00f839b51911e940be08f.png" alt="修改前"></p><p>用开发者工具找到搜索框的元素路径，这里是 <code>#nav-searchform &gt; div.nav-search-content &gt; input</code>。</p><p>删除原有的 css 代码，添加如下代码：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#nav-searchform</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.nav-search-content</span> &gt; <span class="selector-tag">input</span><span class="selector-pseudo">::placeholder</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: transparent;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>浏览器会自动保存并自动刷新，修改后的网页如下：</p><p><img data-src="https://i0.hdslb.com/bfs/album/7d216ad778c1d1415dd9115f37a6f70260f590af.png" alt="修改后"></p><p>而且此后只要通过 Arc 浏览器访问 Bilibili，都会自动隐藏搜索框的推荐搜索。</p><p>是不是很简单！</p><h2 id="修改更多网页"><a href="#修改更多网页" class="headerlink" title="修改更多网页"></a>修改更多网页</h2><p>这三行代码可以隐藏 Bilibili <strong>首页</strong> 上方的推荐搜索，但在其他页面可能不生效。因为 Bilibili 其他页面的结构可能有所不同，所以需要到不生效的页面再次找到对应的元素路径，然后添加 css 代码。</p><p>这里以直播间页面 <a href="https://live.bilibili.com/">https://live.bilibili.com/</a> 的搜索框路径为例：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#nav-searchform</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.p-relative</span><span class="selector-class">.search-bar</span><span class="selector-class">.over-hidden</span><span class="selector-class">.border-box</span><span class="selector-class">.t-nowrap</span> &gt; <span class="selector-tag">input</span><span class="selector-pseudo">::placeholder</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: transparent;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>应该可以隐藏全部搜索框的推荐搜索了。</p><p>说到直播间，默认的 Bilibili 直播间是这样的：</p><p><img data-src="https://i0.hdslb.com/bfs/album/d675a80cd2b2978ee3b45771e9460a0b861306c6.png" alt="Bilibili 默认直播间"></p><p>比如我只想看 v，不喜欢充值、送礼物、打赏、买周边、上舰，那么上面的礼物星球、限时领取、购物提示，以及下面的小橙车、一排礼物、上舰提示、充值按钮，都是我不想看到的。这时就可以找到它们的元素路径，然后添加 css 代码隐藏它们。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#head-info-vm</span> &gt; <span class="selector-tag">div</span> &gt; <span class="selector-tag">div</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.lower-row</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.left-ctnr</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.gift-planet-entry</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-id">#head-info-vm</span> &gt; <span class="selector-tag">div</span> &gt; <span class="selector-tag">div</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.lower-row</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.left-ctnr</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.activity-gather-entry</span><span class="selector-class">.activity-entry</span><span class="selector-class">.s-activity-entry</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-id">#gift-control-vm</span> &gt; <span class="selector-tag">div</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.vertical-middle</span><span class="selector-class">.dp-table</span><span class="selector-class">.section</span><span class="selector-class">.right-part</span><span class="selector-class">.p-relative</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-id">#gift-control-vm</span> &gt; <span class="selector-tag">div</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.left-part-ctnr</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.ecommerce-entry</span><span class="selector-class">.gift-left-part</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-id">#sections-vm</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.section-block</span><span class="selector-class">.f-clear</span><span class="selector-class">.z-section-blocks</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.left-container</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.flip-view</span><span class="selector-class">.p-relative</span><span class="selector-class">.over-hidden</span><span class="selector-class">.w-100</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-id">#shop-popover-vm</span>, <span class="selector-id">#my-dear-haruna-vm</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改后：</p><p><img data-src="https://i0.hdslb.com/bfs/album/9434bae5a17737cf2d8529f4c387e2d5e2c72be4.png" alt="Bilibili 修改后直播间"></p><p>是不是简洁很多！这下可以专心看直播了。</p><p>同理，你可以修改任何网页，只要找到对应的元素路径，然后添加 css 代码就可以了。</p><h2 id="这是怎么实现的？"><a href="#这是怎么实现的？" class="headerlink" title="这是怎么实现的？"></a>这是怎么实现的？</h2><p>实际上，Arc Boost 就是为浏览器添加了一个插件。比如刚才我们添加的对于 bilibili.com 的 Boost，可以在已安装的插件中找到：</p><p><img data-src="https://i0.hdslb.com/bfs/album/0c2f3aff2006a0d43b6543cde2874fb03e3c18a8.png" alt="已安装的插件"></p><p>在插件管理中，可以看到具体信息，包括插件的大小、权限和存储位置等：</p><p><img data-src="https://i0.hdslb.com/bfs/album/3f15f51105a1fc8846e54e7ee0520dc61227ae73.png" alt="Arc Boost 详情"></p><p>上面显示 &lt; 1 MB，其实也就 2 KB。</p><p><img data-src="https://i0.hdslb.com/bfs/album/d366149c8c08efe6aa89c3e0a519c08d043e96d8.png" alt="Arc Boost 文件夹"></p><h2 id="如何发挥-Arc-Boost-更大的作用？"><a href="#如何发挥-Arc-Boost-更大的作用？" class="headerlink" title="如何发挥 Arc Boost 更大的作用？"></a>如何发挥 Arc Boost 更大的作用？</h2><p>得益于 Arc 浏览器优秀的套壳技术，对于已经添加的 Boost，可以在作用的网页右上角的插件列表（Extensions）中看到，且单独以“Boosts”列出，可以一键删除、修改或置顶，不需要单独在 Finder 中打开并修改。</p><p>Arc Boost 的作用远不止于此。本文只介绍了它的最简单的用法 —— 改变网站样式，实际上，正如一开始进入 Boost 所见，它还可以通过编写 JavaScript 代码替换、注入网站内容，或者做任何事情。它的存在将浏览器插件的开发门槛降到了极低，让任何人都可以轻松地编写并使用适合自己的浏览器插件，不依赖其他工具。</p><p><a href="https://arcboosts.com/">https://arcboosts.com/</a> 收录了很多有趣的 Boost，你可以在这里探索适合自己的，或者自己编写一个并上传上去。如果你有更多关于 Arc Boost 或 Arc 浏览器 的想法，欢迎在下方留言交流。</p><h2 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h2><div class="note info"><p>如果你只想看关于本文主题的内容，可以跳过这一部分。</p></div><p>细心的小伙伴应该已经发现了，我的博客域名又又又换了。从最开始的 .xyz 到后来的 .cn，再到现在的 .com，以后应该不会再换了。这次换域名的原因是，之前在 <a href="https://www.value-domain.com/">Value Domain</a> 上面一元买到了 6 个域名，现在的这个域名就是其中之一。.com 域名确实更加通用，而且不用放在国内的阿里云或腾讯云上（相比 .cn），我转移到了 Cloudflare 上，体验也更好。原来的 .cn 域名做了跳转，所以这次迁移暂时也不会影响到原来的链接（为什么说是“暂时”呢，因为 .cn 域名过期之后大概率不会再续费了，所以到时候原来的链接肯定会失效）。唯一受影响的就是之前不蒜子统计的浏览数据了，不过也没什么关系，新的开始，新的计数。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;方便、快捷地自定义网页，无需安装任何插件。&lt;/p&gt;</summary>
    
    
    
    <category term="技术" scheme="https://superpung.com/tech/"/>
    
    
    <category term="Arc 浏览器" scheme="https://superpung.com/tags/Arc-%E6%B5%8F%E8%A7%88%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>我和我的 2022</title>
    <link href="https://superpung.com/rev-2022/"/>
    <id>https://superpung.com/rev-2022/</id>
    <published>2022-12-31T02:41:04.000Z</published>
    <updated>2022-12-31T02:41:04.000Z</updated>
    
    <content type="html"><![CDATA[<p>年更 Blogger 的 2022，是平凡但不平静的一年。</p><span id="more"></span><p>回看了 2021 的年度总结，感叹 2022 确实因各种原因变得更平淡无奇。一方面因为疫情和防控，基本没有出校的机会，枯燥的学校生活让人难以留下深刻的记忆，禁锢的脚步让人难以满足生活的现状，无休止的打扰让人充满无奈和妥协。另一方面由于下半年的考试准备，推辞了很多有趣的事情，也“忍痛割爱”地放弃了坚持许久的探索，而且准备考试的过程更加无聊和乏味。很惭愧，2021 对 2022 充满期待的我未能如愿以偿，在今天的心境下也难以抒发去年今日的积极感。</p><p>机缘巧合下，今年跨年在家里一个人度过。上次在家里跨年还是在 7 年前的初中，已经记忆不清了。之后几年的跨年都是在高中，然后就是在大学了。惟有感慨时光飞快，无数记忆碎片填满了过去的回忆，时间如刀割般在某些时刻断裂，又在整体上相连。太阳在 2022 年最后一次落下，也会在 2023 年第一次升起，希望新的一天可以扫除旧的阴霾，带来新的希望。</p><p>回忆上一个假期，只能想起寒假，而暑假的记忆却很模糊。寒假是和朋友们团聚的假期，在这之前满分通过科四拿到了驾驶证（然而并没有开车…），和朋友们去滑雪，也是第一次。</p><img data-src="https://i0.hdslb.com/bfs/album/526cdd69eb65f4e4223659bf93c26bd21e98d726.jpg" alt="ski-2022" style="zoom:10%;" /><p>这是某次滑下来的场景，滑雪对身体不协调的人来说好难。</p><p>相比之下，暑假生活略显平淡和短暂。一方面朋友们大多忙于实习和工作，或疫情防控原因，或时间原因没能团聚；另一方面暑假开始准备考试，规划下半年的计划，也很少有能自由支配的时间。</p><p>不过从 6 月开始养成的记“日记”习惯让我比较受益。不是传统意义的日记，只是某时某刻想到的一句话、一个灵感、一个经验、一个总结，按时间和方向有意识地记下来，现在回看仍有很大的意义。让我觉得这一年并没有在浑浑噩噩中度过，相反，这一年可能是我思想上提高最大的一年，或多或少得益于我的大部分想法都能记录下来，当串连起来时更能引发新的感悟。</p><p>这一年也看了很多电影和剧，也有意识地记录在了 Notion 里，包括《爱、死亡和机器人》等充满创意的 Series，也有《Friends》等经典美剧，希望新的一年能有更多时间欣赏更多优秀的作品</p><p>暑假打卡了 Apple Store，在 Genius Bar 换了一个键盘，详情可以看 <a href="/genius-bar">这篇文章</a>。</p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">a tour of Genius Bar <a href="https://t.co/omgvOWzDZG">pic.twitter.com/omgvOWzDZG</a></p>&mdash; 𝙎𝙐𝙋𝙀𝙍 (@repusme) <a href="https://twitter.com/repusme/status/1551117378708455427?ref_src=twsrc%5Etfw">July 24, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>同时也换了新 iPad，不巧在闲鱼上遇到了假冒的 Apple Pencil。和骗子斗争几天后，最终成功全身而退，对方也受到注销账号的处罚，我发布的一篇“如何辨别真假 Apple Pencil”的帖子也收获了数万次浏览。</p><p>今年是世界杯年，继初中（2014）、高中（2018）后，是第三次看世界杯。还记得 2018 年世界杯时，还在幻想 4 年后的 2022 我会以什么样的处境和心境看这场比赛。那时对今日的想象很不清楚，此时对彼刻的回忆也很模糊。总之，今年的世界杯决赛非常精彩，恭喜阿根廷、恭喜梅西夺冠 🏆。</p><p>最后记录一下数字总结。使用网易云音乐的第 7 年，也是正式使用 Apple Music 的第一年，所以使用网易云音乐的次数也在减少。</p><p><img data-src="https://i0.hdslb.com/bfs/album/94cef62384e8d5ec2029703d34ab92f7d59e63d6.png" alt="2021-24"></p><p><img data-src="https://i0.hdslb.com/bfs/album/dc3d493bc67161ae50101da2e6a81a5470b5fe72.png" alt="2021-22"></p><img data-src="https://i0.hdslb.com/bfs/album/18d23ec6e1d91a6d387a6452a11557f13f0fcfa4.png" alt="netease-music-2022" style="zoom:36%;" /><p>今年是重度使用 B 站的一年，留下了很多难忘的回忆，有 3 天因为太忙忘记了打开 B 站…</p><table><tr align=center><td><img data-src="https://i0.hdslb.com/bfs/album/ed49fe23ded6dd54d3149c7e11452f9b4c701f9b.png" alt="2021-23" height=500px /></td><td><img data-src="https://i0.hdslb.com/bfs/album/fe48890094cb5f6cf8af29766ee079bf37082d8f.jpg" alt="bilibili-2022" height=500px /></td></tr></table><p>我觉得每年选出一个“年度 app”是很有意思的事情，以后会坚持下去。去年我认为我的年度 app 是 Notion，而今年则是 Arc。Arc 是一个充满革新性的浏览器，解决了 Chrome、Edge 和 Safari 等曾经常用浏览器的许多痛点，比如标签页的管理方式、主页面的布局等，达到了美观度和实用性的统一和提升。当然，它也有很多不足，比如书签管理是我认为它目前最大的短板，The Browser 官方也通过邮件告知我他们正在着手解决这一问题。而我通过转到 Raindrop 重新设计了我的工作流和信息流，从而很大程度上适应了 Arc 在此方面的不足。</p><blockquote><p>hey, here’s an invite to Arc, the browser I was telling you about!</p><p><a href="https://arc.net/gift/c748bf64">https://arc.net/gift/c748bf64</a></p></blockquote><p>由于这个产品的流行度和颠覆性，我还打算选出第二大年度 app —— <a href="https://chat.openai.com/">ChatGPT</a>。作为 OpenAI 的对话机器人，ChatGPT 更有逻辑、有知识，能较为出色地、人性化地解决提出的许多主观问题，再一次为人工智能的力量折服。但在我看来，它并不能替代 Google 成为新一代搜索引擎，因为它虽然能解决好“How”，但不能解决好“What”。比如一些常识性、知识性的问题，它总能“一本正经地胡说八道”，看似很有道理，但在事实上是完全错误的答案。</p><hr><p>除了生活方面，在学习方面进一步学习和发挥了 iOS 开发，参与开发了微北洋 4.2，也是一段很有趣的经历。对 iOS、对前端、对 git、对 cooperation 都有了新的理解。在闲暇之余我开展了 SuperLab 的 2022 年度项目 Galaxy，也是我的第 3 个 app，是包括 iOS 前端和后端完整的项目，有数据处理、音频处理等内容。</p><table><tr align=center><td><img data-src="https://i0.hdslb.com/bfs/album/3a99bb66be5d304e0f490b6067634a9945ae2753.png" alt="wby-2022-1" height=500px/></td><td><img data-src="https://i0.hdslb.com/bfs/album/b1e4d1a7bce830ffe7f7ebdd659ebc1547c2c3d0.png" alt="wby-2022-2" height=500px /></td></tr><tr align=center><td><img data-src="https://i0.hdslb.com/bfs/album/831022018566f7c978a50b8bc37ae694e27d268a.png" alt="galaxy-2022-1" height=500px /></td><td><img data-src="https://i0.hdslb.com/bfs/album/3c0406d1262b87a6f33a49361f526903a85b8406.png" alt="galaxy-2022-2" height=500px /></td></tr></table><p>p1 是微北洋求实论坛一代神贴，被投稿到了某 channel，而图片里的 UI 是我写的，很奇妙的感觉。p2 是“海棠季”粉色主题。p3 和 p4 是 Galaxy 中的部分功能，一个是简单爬校园卡流水的（主要是方便记账），另一个是音乐播放器的功能。</p><p>2022 下半年大部分时间都在准备考试，全年大部分时间也都在图书馆，度过了图书馆的春夏秋冬。</p><table><tr align=center><td><img data-src="https://i0.hdslb.com/bfs/album/3b3fb062fb770ffcb9067e14bfce55541dd890aa.jpg" alt="lib-2022-spring" /></td><td><img data-src="https://i0.hdslb.com/bfs/album/30c508e2798746c784f2a37efbd6efaa21805f90.jpg" alt="lib-2022-summer" /></td></tr><tr align=center><td><img data-src="https://i0.hdslb.com/bfs/album/14d56eb1fba1647275262136c76664cffa67f25c.jpg" alt="lib-2022-autumn" /></td><td><img data-src="https://i0.hdslb.com/bfs/album/cdcfae2b0d0a0e6d23019e03801089e3b7987124.jpg" alt="lib-2022-winter" /></td></tr></table><p>p3 并不是图书馆的秋天，因为图书馆视角的秋天忘记拍照了。由此可见图书馆的窗户该擦了。</p><p><img data-src="https://i0.hdslb.com/bfs/album/d6e02153e684d78f409a49433989780a30314fe0.jpg" alt="river-qingnian-2022"></p><p>最喜欢的是青年湖畔的晚风。</p><p><img data-src="https://i0.hdslb.com/bfs/album/149d6a4a3c7665cbc253be1b3088bdda98f88b83.jpg" alt="kaoyan-2022-night"></p><p>考试前夕的某个夜晚，很安静。</p><p><img data-src="https://i0.hdslb.com/bfs/album/0a8ea659221abf04d319e3ad17173f331c98bb2f.jpg" alt="kaoyan-2022-day"></p><p>23 考研第一天中午，车水马龙。</p><hr><p>最后还是想记录一下 2022 最 upsetting 的事情，疫情，是 2022 年每个中国人都无法回避的话题。我的 2022 从疫情爆发开始，也从疫情爆发结束。21 年 12 月 31 日，我正写着年度总结、准备离校回家，未曾想过改变整个 2022 的疫情正悄然而至。1 月 8 日，奥密克戎首次在天津爆发（天津是国内首个发现奥密克戎境外输入的城市），爆发地恰是我 1 月 1 日去过的地方。第一次感觉和疫情如此接近，而我也在 1 月 10 日开始了一言难尽的隔离生活，直到 15 日。</p><p><img data-src="https://i0.hdslb.com/bfs/album/f6b6a5d2d7b84fc99978ef8532d1b46710b64e87.png" alt="end-of-quarantine"></p><p>当然这只是开始，不是结束。回到学校，开始了一言难尽的上百次（全年）核酸检测。没有疫情要做核酸检测，疫情爆发后就停止核酸检测，这陷入了一个诡异的逻辑，即如果核酸检测是为了防止疫情爆发，那么事实证明它并没有起到任何作用，疫情该爆发还是会爆发；如果核酸检测是为了保障每个人的健康，那么事实是在最容易感染的时刻停止了核酸检测，最终还是靠每个人自测抗原判断是否感染。讽刺的是，当寝室有感染风险时，找管理者要抗原试纸，管理者却说“抗原又不治病”，并没有给。试问核酸检测治病否？</p><img data-src="https://i0.hdslb.com/bfs/album/94f5e63ae4ff0f437822ad78ad853900c7db357a.jpg" alt="pcr-test-2022-rain-1" style="zoom:30%;" /><img data-src="https://i0.hdslb.com/bfs/album/8ff7b144adab470ade959de0940603aa7e3568da.png" alt="pcr-test-2022-rain-2" style="zoom:30%;" /><p>难以忘记的秋雨中的核酸检测，一场温暖的双向奔赴。（ <em>p1 来自微北洋求实论坛，非本人拍摄</em> ）</p><img data-src="https://i0.hdslb.com/bfs/album/bda7af0c30cc1cd55debb815fa81eda81aac6aa1.jpg" alt="wby-2022-csxq" style="zoom:50%;" /><p>来自微北洋求实论坛的一篇短文。</p><img data-src="https://i0.hdslb.com/bfs/album/3bcf43abac6b441eaad723e300e426c7d018f0d5.jpg" alt="unnecessary-poem" style="zoom:30%;" /><p>在 2022，一切都可以是“非必要”的。（ <em>图片来自微博，见水印</em> ）</p><p>没有制约的权力，没有义务的责任。甚至最终 12 月 2 日疫情在学校爆发之时，学生都没有最基本的知情权，那几天是这一年中最混乱的日子。12 月也是最戏剧性的一月，官方的宣传和 11 月时形成 180 度反转，不过说明了宣传者只需要对上负责而无需对下负责，也说明了很多人并没有独立思考的能力。如今，疫情防控的乱象总算是告一段落，但这一年中发生的 <a href="https://zh.wikipedia.org/w/index.php?title=2022%E5%B9%B4%E4%B8%AD%E5%9B%BD%E5%A4%A7%E9%99%86&oldid=75301656">各种事情</a> 难以忘怀。社会运行的真相是什么，官方是不是歌颂太平的工具，其公信力还有几何，今后诸如全员核酸检测之类的无理要求不知还会有多少人听从。曾经引以为傲的制度优势，最终还是背离了“实事求是”。文章写尽太平事，不肯俯首见苍生。太多的事情就不赘述了，有良知的人总会听见、看到、记下，没有良知的人总会视而不见。</p><blockquote><p>如果尖锐的批泙完全消失，</p><p>温和的批评将会变得刺耳。</p><p>如果温和的批评也不被允许，沉默将被认为居心叵测。</p><p>如果沉默也不再允许，赞扬不够卖力将是一种罪行。</p></blockquote><p>我只能记下并活着，或者让活着的记下我，像 * * 爱我一样爱 * *。当然，让人不至于如此难过的，是仍然有很多人有思想、有良知、敢发声，希望你我都能保持这份清醒，心怀希望。</p><p>向 2022 年因未知原因而失去生命的人默哀。</p><hr><p>正如前文所说，2022 是逐渐成熟、理性、清醒的一年，对待同一件事的看法不再像以前一样片面、情绪化、盲目从众。一方面是由于疫情，见得多了，自然就明白了；另一方面是反思自身的问题，找到存在的缺点和差距，每个人应该都会在大学生活将要结束时成长许多。</p><ul><li><strong>一个人的思想蕴含着他的经历</strong>，往往可以通过了解他的经历从而理解他的思想，有不同经历的人，对同一件事的看法可能不同。</li><li><strong>共同做事的人，目的有所不同</strong>，虽然一群人在做同样的事情，但不同人的目的往往不同，人们总是互相利用来达到自身的目的，总之要明确自己的目的，力求达到共识，警惕被人利用。</li><li><strong>投资自身，去做别人做不了的事情</strong>，这也是我 2023 的目标，真正把时间和精力放在提升自己上，不去做人人都能做的事情，多尝试困难、多试错、多挑战自己。</li><li><strong>果断行动，不要拖延</strong>，提高做事的效率和速度，新的一年任务很重，要在思考和行动上都有所提高。</li></ul><p>总之，无论 2022 有多糟糕，在 2023，还是要解放思想、实事求是，团结一致向前看。新年的鞭炮声已经响起，生命的新阶段已经到来。迎接新生和自由，拥抱梦想和彼岸。</p><p><img data-src="https://i0.hdslb.com/bfs/album/7bd9bbc50eeb1cf59fc79a129e2a1ffaa7345970.jpg" alt="rev-2022"></p><p>2022，再见！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;年更 Blogger 的 2022，是平凡但不平静的一年。&lt;/p&gt;</summary>
    
    
    
    <category term="生活" scheme="https://superpung.com/life/"/>
    
    
  </entry>
  
  <entry>
    <title>Genius Bar 两日游</title>
    <link href="https://superpung.com/genius-bar/"/>
    <id>https://superpung.com/genius-bar/</id>
    <published>2022-07-29T12:03:18.000Z</published>
    <updated>2022-07-29T12:03:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 MacBook Air 购买了一年半之后，它的键盘获得了重生。</p><span id="more"></span><p>起因是暑假在家时，突然发现 MacBook 的 fn 键和左 shift 键被卡住了，fn 键尤为严重，导致每次按 F12 都很别扭。<del>可能 fn 键的唯二作用就是 F12 和表情符号了吧。</del>初步怀疑可能是键盘缝隙进入了杂物，尝试了 <a href="https://support.apple.com/zh-cn/HT205662">Apple 推荐的做法</a>，并没有成功。于是准备去 Genius Bar 让天才们修理一下。</p><p>截至目前，天津一共有 3 家 Apple Store，分别是位于南开区的 <a href="https://www.apple.com.cn/retail/tianjinjoycity/">天津大悦城</a>、位于和平区的 <a href="https://www.apple.com.cn/retail/riverside66tianjin/">天津恒隆广场</a> 和位于河西区的 <a href="https://www.apple.com.cn/retail/mixctianjin/">天津万象城</a>。很不巧，最近河西区和南开区先后出现了疫情中高风险区域，为避免因为有“高中风险地区和高中风险区所在县（市、区、旗、直辖市为所在区）旅居史”，我不得不退掉比较容易到达的 Apple 天津大悦城的预约，预约了 Apple 天津恒隆广场。</p><p>预约 Genius Bar 很简单，在 Apple 官网选择故障类型、预约时间段等信息进行预约即可，如果没有预约可能会导致排不上班的情况。预约成功后会收到 Apple 发来的短信和邮件（提醒：为接受 Genius Bar 天才吧服务做好准备），还可以将预约码添加到钱包。取消预约也很简单，进入预约列表点击取消即可。整个预约过程都是免费的。</p><p>根据 Apple 的提示，去 Genius Bar 之前需要备份你的数据，我用 Time Machine 备份了一下。Apple 还提醒需要确保可以提供相关单据（带上身份证）并携带购买凭证。虽然 Apple 说“请务必携带购买凭证”，但我也找不到我的购买凭证了，事实证明最后也并没有要我出示它，Apple Store 可以直接查到你的购买信息。</p><p>24 日我如约 <a href="https://twitter.com/repusme/status/1551117378708455427">来到 Apple Store</a>，在门口签到后坐下等待安排给我的天才到来。对了，在官网预约时应该登录了你的 Apple ID，所以预约的姓名是你的 Apple ID 对应的姓名。几分钟后天才来了，让我拿出 MacBook，问我出现了什么问题。得知键盘问题后，他按了几下发现确实如此，说可以给换个键盘。同意后，他开始对 MacBook 进行检测，关掉了 Find My Mac（据说是为了连接到 Apple Store 的设备）。检测没什么问题，告知我现在键盘并没有现货，需要从上海调配才能给我换，需要一周内时间。我同意，然后他开了维修记录（Apple Store 工作授权），我签字后收到了副本邮件，有维修编号、客户信息、产品信息、问题说明 &#x2F; 诊断、维修估价等信息。当然，维修估价共计 RMB 0。</p><p>第一日游并没有如愿修好我的 MacBook，我也把 MacBook 带回了。在 Apple Store 看到了春季新品，比如指纹收集器暗夜色 MacBook Air M2 和怀旧设计 MacBook Pro M2。指纹收集器是真的容易沾上指纹，显得尤为突兀。不过它的配色是我心目中最好看的 MacBook 配色。</p><p>回去之后，fn 键竟然似乎有些好转了，真是玄学。好几天过去了，Apple 并没有给我打电话，就在我以为 Apple 应该是不能如其到货了的昨天，Apple 打来了电话，问我什么时候有空去送修。我选择了上午的时间，然后就收到了短信和邮件。和前一次预约相同。天才让我做好备份，我又用 Time Machine 备份了一遍。</p><p>29 日我又如约 <a href="https://twitter.com/repusme/status/1552887350383894529">来到 Apple Store</a>，准确说来得稍早了一些，我又摸了一遍 Apple Store 的样机。不得不说，iPhone 13 Pro 和 iPhone 13 Pro Max 的 120 Hz 高刷真的震惊到我了，第一次认真体验才发现如此丝滑。我再拿起旁边的 iPhone 13 和 iPhone 13 mini，竟感觉如此卡顿……（不过多刷一会 60 Hz 也就习惯了）（反转了，后悔没有买 iPhone 13 Pro &gt;_&lt;）Mac Studio 是真的重。iMac 的大下巴是真的丑。MacBook Pro 16 英寸新款也丑，键盘的黑色底色尤为突兀，而且在那么大的 C 面上留那么小的键盘区域，感觉比例有些失调。</p><p>摸了半天，终于到我了。签到后又等着天才来找我，过来后又检测了一遍，和第一次来一样。最后签了字开了条就让我走了。理论上最快 4 小时可以换好，据天才说 M1 款的维修比 Intel 款的简单，而且维修后的检测时间也比 Intel 款的更短（0.5h &lt; 2~3h）。将信将疑下，他说最快今晚可以取回，比较稳妥是明天。一番交谈过后，我离开了 Apple Store（该吃午饭了）。</p><p>由于不想再浪费一天来个“三日游”，坐地铁时间还挺长的，我打算到处转转等今晚拿下。在 M 吃完午饭后回恒隆广场转了一圈，看到了无人问津的小米之家。体验了一下 12 S Ultra，<del>广告是真的多，</del>发现小米的触感反馈做得也很不错了，感觉比 iPhone 的触感体验还要好。恒隆广场挺大的，走一会我又回到了 Apple Store。过了一段时间 Apple 打来了电话，是下午 4:03，说我的 MacBook 修好了可以取货。看来天才诚不欺我，说 4 个小时就 4 个小时。等了一会后，天才拿着我的 MacBook 过来了，让我试一试键盘，试了一下挺好的<del>，像新的一样</del>。然后看了一下我的身份证，签字后就拿走了。</p><p>给我拿 iPad 的小哥让我印象深刻，他是一位听障人士，靠打字和我交流，能感受到他的紧张但很认真。很感动，致敬每一位努力生活的人，也致敬 Apple 强大的辅助功能为残障人士带来了科技的温暖。</p><p>回归正题，最后发现我的 MacBook 应该是整个 C 面都换新了，包括键盘和触控板，顺带给清洁了屏幕（忘了问天才是如何把屏幕清洁得这么干净的了）。<del>当然，最希望电池也能直接给换了，但并没有。</del>总之这次 Genius Bar 的体验还是很棒的，全过程没有任何数据丢失，0 成本（除去路费）九舍一入拿到了“全新”的 MacBook Air，感觉又可以再战几年了（希望 256 GB 硬盘能多撑一会）。</p><blockquote><p>怀念 2019 年在滨江道散步的夜晚，Apple 天津恒隆广场店人潮涌动（当时靠马路边的门是开的），那种景象大抵是回不去了罢。</p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 MacBook Air 购买了一年半之后，它的键盘获得了重生。&lt;/p&gt;</summary>
    
    
    
    <category term="生活" scheme="https://superpung.com/life/"/>
    
    
    <category term="Apple" scheme="https://superpung.com/tags/Apple/"/>
    
  </entry>
  
  <entry>
    <title>实训小记</title>
    <link href="https://superpung.com/training-report/"/>
    <id>https://superpung.com/training-report/</id>
    <published>2022-06-18T03:15:23.000Z</published>
    <updated>2022-06-18T03:15:23.000Z</updated>
    
    <content type="html"><![CDATA[<p>简单记录一下一个多月的“实训”。</p><span id="more"></span><h2 id="1-Linux-虚拟网络基础"><a href="#1-Linux-虚拟网络基础" class="headerlink" title="1 Linux 虚拟网络基础"></a>1 Linux 虚拟网络基础</h2><h3 id="1-1-tap"><a href="#1-1-tap" class="headerlink" title="1.1 tap"></a>1.1 tap</h3><p>tap 和 tun 是操作系统内核中的虚拟网络设备，tap 位于二层，tun 位于三层。它们的数据结构如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">tun_struct</span> &#123;</span></span><br><span class="line">    <span class="type">char</span> name[<span class="number">8</span>];                <span class="comment">// 设备名</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> flags;         <span class="comment">// 区分tun和tap设备</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">fasync_struct</span> *<span class="title">fasync</span>;</span> <span class="comment">// 文件异步通知结构</span></span><br><span class="line">    <span class="type">wait_queue_head_t</span> read_wait; <span class="comment">// 等待队列</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">net_device</span> <span class="title">dev</span>;</span>       <span class="comment">// Linux抽象网络设备结构</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sk_buff_head</span> <span class="title">txq</span>;</span>     <span class="comment">// 网络缓冲区队列</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">net_device_stats</span> <span class="title">stats</span>;</span> <span class="comment">// 网卡状态信息结构</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>tap 和 tun 的数据结构定义相同，两者仅通过一个 Flag 来区分。但二者承载的功能区别很大：</p><ul><li>tap 位于网络 OSI 模型的二层（数据链路层）</li><li>tun 位于网络的三层</li></ul><p>tap 从功能定位上来讲，位于数据链路层，数据链路层的主要协议有：</p><ol><li>点对点协议（Point-to-Point Protocol）</li><li>以太网（Ethernet）</li><li>高级数据链路协议（High-Level Data Link Protocol）</li><li>帧中继（Frame Relay）</li><li>异步传输模式（Asynchronous Transfer Mode）</li></ol><p>tap 只与其中一种协议——以太网（Ethernet）协议对应。所以 tap 有时也称为“虚拟以太设备”。</p><p>创建 tap 的方法：</p><ol><li>Linux 使用 tun 模块实现了 tun&#x2F;tap，所以首先 Linux 得有 tun 模块，使用 <code>modinfo tun</code> 检查</li><li>使用 <code>lsmod | grep tuntun</code> 检查 tun 模块是否已经加载</li><li>确认 Linux 是否有操作 tun&#x2F;tap 的命令行工具 tunctl，安装方式 <code>yum install tunctl</code></li><li>创建一个 tap 设备：<code>tunctl -t tap_test</code></li><li>查看刚刚创建的 tap：<code>ip link list</code> 或 <code>ifconfig -a</code></li><li>绑定IP地址：<code>ip addr add local 192.168.100.1/24 dev tap_test</code> 或 <code>ifconfig tap_test 192.168.100.1/24</code></li><li>创建完成</li></ol><h3 id="1-2-namespace"><a href="#1-2-namespace" class="headerlink" title="1.2 namespace"></a>1.2 namespace</h3><p>传统的 Linux 的许多资源是全局的，namespace 的目的首先就是将这些资源做资源隔离。Linux 可以在一个 Host 内创建许多 namespace，不同 namespace 的资源互相不可见、彼此透明。namespace 示意图如下所示：</p><p><img data-src="https://i0.hdslb.com/bfs/album/522a582e554e47577e053af295796565eea5e60d.png" alt="r-1-2-1"></p><p>从网络的视角来看，一个 namespace 提供了一份独立的网络协议栈（网络设备接口、IPv4、IPv6、IP路由、防火墙规则、sockets 等）。一个设备（Linux Device）只能位于一个 namespace 中，不同 namespace 中的设备可以利用 veth  pair 进行桥接。</p><p>namespace 操作：</p><ol><li>查看当前的 namespace 列表：<code>ip netns list</code></li><li>创建一个 namespace，名字是 ns_test：<code>ip netns add ns_test</code></li><li>把原来创建的虚拟设备 tap_test 迁移到这个 namespace 里去：<code>ip link set tap_test netns ns_test</code></li><li>查看或操作 namespace 里面的设备：<code>ip [-all] netns exec [NAME] cmd ...</code></li></ol><h3 id="1-3-veth-pair"><a href="#1-3-veth-pair" class="headerlink" title="1.3 veth pair"></a>1.3 veth pair</h3><p>veth  pair 不是一个设备，而是一对设备，以连接两个虚拟以太端口。操作 veth  pair，需要跟 namespace 一起配合，否则没有意义。</p><p>veth pair 简单示例：</p><p><img data-src="https://i0.hdslb.com/bfs/album/b77067976c8865d6120f2e9d2094f1f226b79b32.png" alt="r-1-3-1"></p><p>创建简单示例：</p><ol><li>创建 veth pair：<code>ip link add tap1 type veth peer name tap2</code></li><li>创建 namespace：<code>ip netns add ns1ip netns add ns2</code></li><li>把两个 tap 分别迁移到对应的 namespace 中：<code>ip link set tap1 netns ns1ip link set tap2 netns ns2</code></li><li>分别给两个 tap 绑定 IP 地址：<code>ip netns exec ns1 ip addr add local 192.168.50.1/24 dev tap1</code>、<code>ip netns exec ns2 ip addr add local 192.168.50.2/24 dev tap2</code></li><li>将两个 tap 设置为 up：<code>ip netns exec ns1 ifconfig tap1 up</code>、<code>ip netns exec ns2 ifconfig tap2 up</code></li><li>ping：<code>ip netns exec ns2 ping 192.168.50.1</code>、<code>ip netns exec ns1 ping 192.168.50.2</code></li></ol><h3 id="1-4-Bridge"><a href="#1-4-Bridge" class="headerlink" title="1.4 Bridge"></a>1.4 Bridge</h3><p>Linux 中 Bridge（网桥）即为 Switch（交换机）。Linux 实现 Bridge 功能的是 brctl 模块，相关用法如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">brctlUsage: brctl [commands]</span><br><span class="line">commands:</span><br><span class="line">addbr         &lt;bridge&gt;              add bridge</span><br><span class="line">    delbr         &lt;bridge&gt;              delete bridge</span><br><span class="line">    addif         &lt;bridge&gt; &lt;device&gt;     add interface to bridge</span><br><span class="line">    delif         &lt;bridge&gt; &lt;device&gt;     delete interface from bridge</span><br><span class="line">    hairpin       &lt;bridge&gt; &lt;port&gt; &#123;on|off&#125;     turn hairpin on/off</span><br><span class="line">    setageing     &lt;bridge&gt; &lt;<span class="keyword">time</span>&gt;              <span class="built_in">set</span> ageing <span class="keyword">time</span></span><br><span class="line">    setbridgeprio &lt;bridge&gt; &lt;prio&gt;              <span class="built_in">set</span> bridge priority</span><br><span class="line">    setfd         &lt;bridge&gt; &lt;<span class="keyword">time</span>&gt;              <span class="built_in">set</span> bridge forward delay</span><br><span class="line">    sethello      &lt;bridge&gt; &lt;<span class="keyword">time</span>&gt;              <span class="built_in">set</span> hello <span class="keyword">time</span></span><br><span class="line">    setmaxage     &lt;bridge&gt; &lt;<span class="keyword">time</span>&gt;              <span class="built_in">set</span> max message age</span><br><span class="line">    setpathcost   &lt;bridge&gt; &lt;port&gt; &lt;cost&gt;       <span class="built_in">set</span> path cost</span><br><span class="line">    setportprio   &lt;bridge&gt; &lt;port&gt; &lt;prio&gt;       <span class="built_in">set</span> port priority</span><br><span class="line">    show          [ &lt;bridge&gt; ]          show a list of bridges</span><br><span class="line">    showmacs      &lt;bridge&gt;              show a list of mac addrs</span><br><span class="line">    showstp       &lt;bridge&gt;              show bridge stp info</span><br><span class="line">    stp           &lt;bridge&gt; &#123;on|off&#125;     turn stp on/off</span><br></pre></td></tr></table></figure><p>veth pair 综合示例：</p><img data-src="https://i0.hdslb.com/bfs/album/1d0632762bd13e4185224c31efebc2e70ec77184.png" alt="r-1-4-1" style="zoom:50%;" /><p>实现这个用例：</p><ol><li>创建 veth pair：<code>ip link add tap1 type veth peer name tap1_peer</code>、<code>ip link add tap2 type veth peer name tap2_peer</code>、<code>ip link add tap3 type veth peer name tap3_peer</code>、<code>ip link add tap4 type veth peer name tap4_peer</code></li><li>创建 namespace：<code>ip netns add ns1</code>、<code>ip netns add ns2</code>、<code>ip netns add ns3</code>、<code>ip netns add ns4</code></li><li>把 tap 迁移到相应 namespace 中：<code>ip link set tap1 netns ns1</code>、<code>ip link set tap2 netns ns2</code>、<code>ip link set tap3 netns ns3</code>、<code>ip link set tap4 netns ns4</code></li><li>创建 Bridge：<code>brctl addbr br1</code></li><li>把相应 tap 添加到 Bridge 中：<code>brctl addif br1 tap1_peer</code>、<code>brctl addif br1 tap2_peer</code>、<code>brctl addif br1 tap3_peer</code>、<code>brctl addif br1 tap4_peer</code></li><li>配置相应 tap 的 IP 地址：<code>ip netns exec ns1 ip addr add local 192.168.50.1/24 dev tap1</code>、<code>ip netns exec ns2 ip addr add local 192.168.50.2/24 dev tap2</code>、<code>ip netns exec ns3 ip addr add local 192.168.50.3/24 dev tap3</code>、<code>ip netns exec ns4 ip addr add local 192.168.50.4/24 dev tap4</code></li><li>将 Bridge 及所有 tap 状态设置为 up：<code>ip link set br1 up</code>、<code>ip link set tap1_peer up</code>、<code>ip link set tap2_peer up</code></li></ol><h3 id="1-5-Router"><a href="#1-5-Router" class="headerlink" title="1.5 Router"></a>1.5 Router</h3><p>Linux 就是路由器（Router），修改配置文件 “&#x2F;etc&#x2F;sysctl.conf”， 将 <code>net.ipv4.ip_forward = 0</code> 修改为 <code>1</code>，保存后退出，即打开了路由转发功能。</p><p>查看路由表：<code>route -nee</code>，添加静态路由：<code>route add -net [ip] netmask [netmask] gw [gateway]</code>。</p><h3 id="1-6-tun"><a href="#1-6-tun" class="headerlink" title="1.6 tun"></a>1.6 tun</h3><p>tun 是一个网络层（IP）的点对点设备，它启用了 IP 层隧道功能。Linux 原生支持的三层隧道，可以通过命令行 <code>ip tunnel help</code> 查看：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">ip tunnel <span class="built_in">help</span></span><br><span class="line">Usage: ip tunnel &#123; add | change | del | show | prl | 6rd &#125; [ NAME ]</span><br><span class="line">[ mode &#123; ipip | gre | sit | isatap | vti &#125; ] [ remote ADDR ] [ <span class="built_in">local</span> ADDR ]</span><br><span class="line">    [ [i|o]<span class="built_in">seq</span> ] [ [i|o]key KEY ] [ [i|o]csum ]</span><br><span class="line">    [ prl-default ADDR ] [ prl-nodefault ADDR ] [ prl-delete ADDR ]</span><br><span class="line">    [ 6rd-prefix ADDR ] [ 6rd-relay_prefix ADDR ] [ 6rd-reset ]</span><br><span class="line">    [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]</span><br><span class="line">Where: NAME := STRING</span><br><span class="line">ADDR := &#123; IP_ADDRESS | any &#125;</span><br><span class="line">    TOS  := &#123; STRING | 00..ff | inherit | inherit/STRING | inherit/00..ff &#125;</span><br><span class="line">    TTL  := &#123; 1..255 | inherit &#125;</span><br><span class="line">    KEY  := &#123; DOTTED_QUAD | NUMBER &#125;</span><br></pre></td></tr></table></figure><p>可以看到，Linux 一共原生支持 5 种三层隧道（tunnel）：</p><table><thead><tr><th>隧道</th><th>简述</th></tr></thead><tbody><tr><td>ipip</td><td>IP in IP，在 IPv4 报文的基础上再封装一个 IPv4 报文头，属于IPv4 in IPv4</td></tr><tr><td>gre</td><td>通用路由封装（Generic Routing Encapsulation），定义了在任意一种网络层协议上封装任意一个其他网络层协议的协议，属于 IPv4&#x2F;IPv6 over IPv4</td></tr><tr><td>sit</td><td>这个跟 ipip 类似，只不过是用一个 IPv4 的报文头封装 IPv6 的报文，属于 IPv6 over IPv4</td></tr><tr><td>isatap</td><td>站内自动隧道寻址协议，一般用于 IPv4 网络中的 IPv6&#x2F;IPv4 节点间的通信</td></tr><tr><td>vti</td><td>全称是 Virtual Tunnel Interface，为 IPsec 隧道提供了一个可路由的接口类型</td></tr></tbody></table><h3 id="1-7-iptables"><a href="#1-7-iptables" class="headerlink" title="1.7 iptables"></a>1.7 iptables</h3><p>通过 iptables 相关的命令行，可以实现了防火墙、NAT 的功能，而这种功能的实现是通过运行在内核空间的 netfilter 模块完成的，它们之间的关系如下所示：</p><img data-src="https://i0.hdslb.com/bfs/album/a738684e1c9ff094909a5de2d14d7f55c0d5bb82.png" alt="r-1-7-1" style="zoom:50%;" /><p>iptables 内置了三张表：filter、nat 和 mangle。filter 和 nat 是为了实现防火墙和 NAT 功能而服务的，mangle 主要应用在修改数据包内容上，用来做流量整形。iptables 还内置了另外 2 张表 raw 和 security，这里不详细介绍了。</p><p>iptables 内置的既是三张表，也是三条链（chain），也是三种策略（policy），这些策略由不同规则（rule）串接而成。</p><p><code>iptables -A INPUT -i eth0 -p icmp -j ACCEPT</code> 这条规则表达的意思是：允许所有从eth0 端口进入且协议是 ICMP 的报文可以接受（可以进入下一个流程）的。</p><h4 id="1-7-1-NAT"><a href="#1-7-1-NAT" class="headerlink" title="1.7.1 NAT"></a>1.7.1 NAT</h4><p>NAT（Network Address Translation，网络地址转换），顾名思义，就是从一个 IP 地址转换为另一个 IP 地址。当然，这里面的根本原因还是IP地址不够用的问题（解决 IP 地址枯竭的方法一个是 IPv6，另一个就是 NAT）。</p><p>NAT 从实现技术角度分为：静态 NAT、动态 NAT 和端口多路复用三种方案：</p><ol><li><p>静态 NAT（Static NAT）</p><p> 静态 NAT 有两个特征：</p><ol><li>私网IP地址与公网IP地址的转换规则是静态指定的，比如 10.10.10.1 与 50.0.0.1 互相转换，这个是静态指定好的；</li><li>私网 IP 地址与公网 IP 地址是 1∶1，即一个私网 IP 地址对应 1 个公网 IP 地址。</li></ol></li><li><p>动态 NAT</p><p> 一般情况是公网 IP 比私网 IP 地址少的时候，用到动态 NAT 方案。动态 NAT，就是一批私网 IP 与公网 IP 地址之间不是固定的转换关系，而是在 IP 报文处理过程中由 NAT 模块进行动态匹配。虽然，公网 IP 比私网 IP 地址少，但是，同时在线的私网 IP 需求小于等于公网 IP 数量，不然某些私网 IP 将得不到正确的转换，从而导致网络通信失败。</p><p> 动态 NAT，有三个特征：</p><ol><li>私网与公网IP地址之间不是固定匹配转换的，而是变化的；</li><li>两者之间的转换规则不是静态指定的，而是动态匹配的；</li><li>私网 IP 地址与公网 IP 地址之间是 m∶n，一般 m &lt; n。</li></ol></li><li><p>端口多路复用 &#x2F; PAT</p><p> 如果私网 IP 地址有多个，而公网 IP 地址只有一个，那么，静态 NAT 显然是不行了，动态 NAT 也基本不行（只有一个公网 IP，不够用）。此时，就需要用到端口多路复用。多个私网 IP 映射到同一个公网 IP，不同的私网 IP 利用端口号进行区分，这里的端口号指的是 TCP&#x2F;UDP 端口号。所以端口复用又叫 PAT（Port Address Translation）。</p><p> 端口多路复用（PAT）的特征是：</p><ol><li>私网 IP：公网 IP &#x3D; m∶1；</li><li>以公网 IP + 端口号来区分私网 IP。</li></ol></li><li><p>SNAT &#x2F; DNAT</p><p> 前面说的静态 NAT（Static NAT）和动态 NAT不能简称 SNAT、DNAT，因为 SNAT&#x2F;DNAT 有另外的含义，是另外的缩写。要区分SNAT（Source Network Address Translation，源地址转换）与DNAT（Destination Network Address Translation，目的地址转换）这两个功能可以简单地由连接发起者是谁来区分。</p><ol><li>内部地址要访问公网上的服务时（如 Web访问），内部地址会主动发起连接，由路由器或者防火墙上的网关对内部地址做个地址转换，将内部地址的私有 IP 转换为公网的公有 IP，网关的这个地址转换称为 SNAT，主要用于内部共享 IP 访问外部。</li><li>当内部需要提供对外服务时（如对外发布 Web 网站），外部地址发起主动连接，由路由器或者防火墙上的网关接收这个连接，然后将连接转换到内部，此过程是由带有公网 IP 的网关替代内部服务来接收外部的连接，然后在内部做地址转换，此转换称为 DNAT，主要用于内部服务对外发布。</li></ol></li></ol><p>Linux 内核空间 Netfilter 模块的 NAT 处理，一共有三个 Chain（处理时刻点）：</p><table><thead><tr><th>流</th><th>流描述</th><th>Chain</th><th>NAT类型</th><th>NAT说明</th></tr></thead><tbody><tr><td>流1</td><td>流从外部到达 Linux 用户空间（私网 IP）</td><td>PREROUTING</td><td>DNAT</td><td>将目的 IP 从公网 IP（Linux 内核空间对应的 IP）转换到私网 IP（Linux 用户空间对应的 IP）</td></tr><tr><td>流2</td><td>流从 Linux 用户空间（私网 IP）到达外部</td><td>POSTROUTING</td><td>SNAT</td><td>将源 IP 从私网 IP（Linux 用户空间对应的 IP）转换到公网 IP（Linux 内核空间对应的 IP）</td></tr><tr><td>流3</td><td>流从 Linux 内核空间（公网 IP）到达外部</td><td>OUTPUT</td><td>DNAT</td><td></td></tr></tbody></table><h4 id="1-7-2-Firewall"><a href="#1-7-2-Firewall" class="headerlink" title="1.7.2 Firewall"></a>1.7.2 Firewall</h4><p>iptables 中的 Firewall（防火墙）概念，属于网络防火墙的概念。iptables 中的防火墙的规则就是基于 TCP&#x2F;IP 协议栈的规则，所以我们称之为网络防火墙。这些规则有：</p><ol><li>in-interface（入网络接口名），数据包从哪个网络接口进入；</li><li>out-interface（出网络接口名），数据包从哪个网络接口输出；</li><li>protocol（协议类型），数据包的协议，如 TCP、UDP 和 ICMP 等；</li><li>source（源地址（或子网）），数据包的源IP地址（或子网）；</li><li>destination（目标地址（或子网）），数据包的目标IP地址（或子网）；</li><li>sport（源端口号），数据包的源端口号；</li><li>dport（目的端口号），数据包的目的端口号。</li></ol><p>符合这些规则的，可以设置为通过（ACCEPT），反之，则不通过（DROP）。或者，符合这些规则的，设置为不通过（DROP）；反之，则通过（ACCEPT）。</p><p>Netfilter 中的 Firewall，会在三个时刻点，进行处理，如下图所示：</p><p><img data-src="https://i0.hdslb.com/bfs/album/2b3844ae03bd7f9038194d8ac2c45ad7b56a3348.png" alt="r-1-7-2"></p><h4 id="1-7-3-mangle"><a href="#1-7-3-mangle" class="headerlink" title="1.7.3 mangle"></a>1.7.3 mangle</h4><p>mangle 表主要用于修改数据包的 ToS（Type  of  Service，服务类型）、TTL（Time  to  Live，生存周期）以及为数据包设置 Mark 标记，以实现 QoS（Quality  of  Service，服务质量）调整以及策略路由等应用。</p><p>netfilter 模块中的 mangle 处理的时刻点如下图所示：</p><p><img data-src="https://i0.hdslb.com/bfs/album/f881ce8fdc2241ea2f99a3ea20a2ddae3d142d90.png" alt="r-1-7-3"></p><h3 id="1-8-Linux-虚拟网络基础总结"><a href="#1-8-Linux-虚拟网络基础总结" class="headerlink" title="1.8 Linux 虚拟网络基础总结"></a>1.8 Linux 虚拟网络基础总结</h3><p>tap、tun、veth  pair 在 Linux 中都被称为设备，但是在与日常概念的类比中，常常被称作接口。Neutron 利用这些“接口”进行 Bridge 之间的连接、Bridge 与 VM（虚拟机）的连接、Bridge 与 Router之间的连接。三者与物理网卡之间的对比关系，如下图所示：</p><p><img data-src="https://i0.hdslb.com/bfs/album/47af2c82d9a2a14309154aec09c026e6fe0e2fc9.png" alt="r-1-8-1"></p><p>Router、Bridge 这些在 Linux 中没有被称为设备的网络功能，反而在日常概念中常常被称为设备。Bridge 提供二层转发功能，Router 提供三层转发功能。Router 还常常借助 iptable 提供 SNAT&#x2F;DNAT 功能。Bridge 也常常借助 iptable 提供 Firewall 功能。</p><p>在 Neutron中，隔离是一个非常重要的特性，利用 namespace 做隔离也是 Neutron 的一个非常重要的手段。</p><h2 id="2-Neutron-原理介绍"><a href="#2-Neutron-原理介绍" class="headerlink" title="2 Neutron 原理介绍"></a>2 Neutron 原理介绍</h2><h3 id="2-1-Neutron-概述"><a href="#2-1-Neutron-概述" class="headerlink" title="2.1 Neutron 概述"></a>2.1 Neutron 概述</h3><p>Neutron 是 OpenStack 项目中负责提供网络服务的组件，它基于软件定义网络的思想，实现了网络虚拟化下的资源管理。Neutron 的设计目标是实现“网络即服务”，为了达到这一目标，在设计上遵循了基于“软件定义网络”实现网络虚拟化的原则，在实现上充分利用了 Linux 系统上的各种网络相关的技术。</p><p>Neutron管理下面的实体：</p><ul><li>网络：隔离的 L2 域，可以是虚拟、逻辑或交换。</li><li>子网：隔离的 L3 域，IP 地址块。其中每个机器有一个 IP，同一个子网的主机彼此 L3 可见。</li><li>端口：网络上虚拟、逻辑或交换端口。<br>  所有这些实体都是虚拟的，拥有自动生成的唯一标示id，支持CRUD功能，并在数据库中跟踪记录状态。</li></ul><h3 id="2-2-实现模式"><a href="#2-2-实现模式" class="headerlink" title="2.2 实现模式"></a>2.2 实现模式</h3><p>无论哪种具体的网络虚拟化实现，一个简化和抽象后的系统架构可以表述为下图所示。</p><p><img data-src="https://i0.hdslb.com/bfs/album/c5afc449e956e6b4156b8b8259bb7ea8bccc6fb9.png" alt="r-abstract_arch"></p><p>在启用 DVR 特性（J 版本以后支持）之前，所有流量（东西向、南北向）都需要经过网络节点的转发；DVR 特性则允许东西向流量和带有 Floating IP 的南北向流量不经过网络节点的转发，直接从计算节点的外部网络出去。</p><p>网络节点有且仅有 Neutron 服务，就是网络服务。Neutron 主要负责管理私有网段和公有网段之间的通信，同时管理虚拟机网络之间的通信以及防火墙等等。一般在部署时会部署两个以上的网络端口，分别用于与控制节点通信、同计算&#x2F;存储节点通信、用于外部的虚拟机与相应的网络之间的通信。</p><p>计算节点主要包含计算服务、网络服务以及监控服务。计算节点对所部署的虚拟机提供基本的网络功能支持，包括隔离不同租户的虚拟机和进行一些基本的安全策略管理。计算节点包含 Nova，Neutron，Telemeter 三个服务：</p><ul><li>基础服务 Nova：提供虚拟机的创建，运行，迁移，快照等各种围绕虚拟机的服务，并提供 API 与控制节点对接，由控制节点下发任务</li><li>基础服务 Neutron：提供计算节点与网络节点之间的通信服务</li><li>扩展服务 Telmeter：提供计算节点的监控代理，将虚拟机的情况反馈给控制节点，是 Centimeter 的代理服务</li></ul><h4 id="2-2-1-GRE-模式"><a href="#2-2-1-GRE-模式" class="headerlink" title="2.2.1 GRE 模式"></a>2.2.1 GRE 模式</h4><p>在 OpenStack 中网络实现的一个简化的架构示意：</p><p><img data-src="https://i0.hdslb.com/bfs/album/637cd7fc907ee5c793f2548d8c33c82c06493e84.png" alt="r-basic_arch_gre"></p><p>计算节点上包括两台虚拟机 VM1 和 VM2，分别经过一个网桥（如 qbr-XXX）连接到 br-int 网桥上。br-int 网桥再经过 br-tun 网桥（物理网络是 GRE 实现）连接到物理主机外部网络。对于物理网络通过 vlan 来隔离的情况，则一般会存在一个 br-eth 网桥，替代 br-tun 网桥。</p><p>br-tun将带有 vlan tag 的 vm 跟外部通信的流量转换到对应的 gre 隧道，这上面要实现主要的转换逻辑，规则要复杂，一般通过多张表来实现，这些规则所组成的整体转发逻辑如下图所示。</p><p><img data-src="https://i0.hdslb.com/bfs/album/d0cabd01553ddafa46d44f0a79d3f922aa75b154.png" alt="r-compute_br_tun_fwd_logic"></p><p>计算节点上发往 GRE 隧道的网包最终抵达网络节点上的 br-tun，该网桥的规则跟计算节点上 br-tun 的规则相似，完成 tunnel 跟 vlan 之间的转换。br-int 上挂载了很多进程来提供网络服务，包括路由器、DHCP服务器等。这些进程不同的租户可能都需要，彼此的地址空间可能冲突，也可能跟物理网络的地址空间冲突，因此都运行在独立的网络名字空间中。规则跟计算节点的br-int规则一致，表现为一个正常交换机。</p><h4 id="2-2-2-VLAN-模式"><a href="#2-2-2-VLAN-模式" class="headerlink" title="2.2.2 VLAN 模式"></a>2.2.2 VLAN 模式</h4><p>Vlan 模式下的系统架构跟 GRE 模式下类似，如下图所示。</p><p><img data-src="https://i0.hdslb.com/bfs/album/e7342f3cb10eff054eab1572b4dfc4c27edcd573.png" alt="r-basic_arch_vlan"></p><p>需要注意的是，在 vlan 模式下，vlan tag 的转换需要在 br-int 和 br-ethx 两个网桥上进行相互配合。即 br-int 负责从 int-br-ethX 过来的包（带外部 vlan）转换为内部 vlan，而 br-ethx 负责从 phy-br-ethx 过来的包（带内部 vlan）转化为外部的 vlan。</p><p>在计算节点中，类似 GRE 模式下，br-int 负责租户隔离，br-eth1 负责跟计算节点外的网络通信。在 Vlan 模式下，租户的流量隔离是通过 vlan 来进行的，因此此时包括两种 vlan，虚拟机在计算内流量带有的 local vlan 和在计算之外物理网络上隔离不同租户的 vlan。br-int 和 br-eth1 分别对从端口 int-br-eth1 和 phy-br-eth1 上到达的网包进行 vlan tag 的处理。此处有两个网，分别带有两个 vlan tag（内部 tag1 对应外部 tag101，内部 tag2 对应外部 tag102 ）。其中，安全组策略仍然在 qbr 相关的 iptables 上实现。</p><p>在网络节点中，类似 GRE 模式下，br-eth1 收到到达的网包，int-br-eth1 和 phy-br-eth1 上分别进行 vlan 转换，保证到达 br-int 上的网包都是带有内部 vlan tag，到达 br-eth1 上的都是带有外部 vlan tag。br-ex 则完成到 OpenStack 以外网络的连接。</p><h4 id="2-2-3-VXLAN-模式"><a href="#2-2-3-VXLAN-模式" class="headerlink" title="2.2.3 VXLAN 模式"></a>2.2.3 VXLAN 模式</h4><p>VXLAN 模式下，网络的架构跟 GRE 模式类似，所不同的是，不同节点之间通过 VXLAN 隧道互通，即虚拟化层是采用的 VXLAN 协议，基本结构如下图所示。</p><p><img data-src="https://i0.hdslb.com/bfs/album/9e87e5916e5f34ecaa1227254b42468e41380240.png" alt="r-vxlan"></p><p>计算节点主要包括两个网桥：集成网桥 br-int 和 隧道网桥 br-tun，网络节点担负着进行网络服务的任务，包括DHCP、路由和高级网络服务等，一般包括三个网桥：br-tun、br-int 和 br-ex。</p><h3 id="2-3-使用的服务"><a href="#2-3-使用的服务" class="headerlink" title="2.3 使用的服务"></a>2.3 使用的服务</h3><ol><li><p>网络命名空间</p><p> 在 Linux 中，网络名字空间可以被认为是隔离的拥有单独网络栈（网卡、路由转发表、iptables）的环境。网络名字空间经常用来隔离网络设备和服务，只有拥有同样网络名字空间的设备，才能看到彼此。</p></li><li><p>DHCP 服务</p><p> dhcp 服务是通过 dnsmasq 进程（轻量级服务器，可以提供 dns、dhcp、tftp 等服务）来实现的，该进程绑定到 dhcp 名字空间中的 br-int 的接口上。可以查看相关的进程。</p></li><li><p>路由服务</p><p> router 是提供跨 subnet 的互联功能的。比如用户的内部网络中主机想要访问外部互联网的地址，就需要 router 来转发（因此，所有跟外部网络的流量都必须经过 router）。目前router 的实现是通过 iptables 进行的。</p></li><li><p>安全组</p><p> 安全组通过 Linux IPtables 来实现，为此，在计算节点上引入了 qbr* 这样的 Linux 传统 bridge（iptables 规则目前无法加载到直接挂在到 ovs 的 tap 设备上），整体逻辑如下图所示：</p><p> <img data-src="https://i0.hdslb.com/bfs/album/648a26b2b03b6a23563200e25b617ca286f43bbb.png" alt="r-sg_global_logic"></p></li><li><p>负载均衡即服务</p><p> 负载均衡即服务（Load Balance as a Service，LBaaS）是一项网络高级服务。它允许租户动态的在自己的网络创建一个负载均衡设备，可以说是分布式系统中比较基础的组件，它接收前端过来的请求，然后将请求按照某种均衡的策略转发给后端资源池中的某个处理单元，以完成处理。进而可以实现高可用性和横向的扩展性。OpenStack Neutron 通过高级服务扩展的形式支持 LBaaS，目前默认是通过 HAProxy 软件来实现的。</p></li><li><p>防火墙即服务</p><p> 防火墙即服务（FireWall as a Service）在网络节点上（具体说来是在路由器命名空间中）来实现。目前，OpenStack 中实现防火墙基于 Linux 系统自带的 iptables 实现。一个可能混淆的概念是安全组（Security Group），安全组的对象是虚拟网卡，由 L2 Agent 来实现，比如 neutron_openvswitch_agent 和 neutron_linuxbridge_agent，会在计算节点上通过配置 iptables 规则来限制虚拟网卡的进出访问。防火墙可以在安全组之前隔离外部过来的恶意流量，但是对于同个子网内部不同虚拟网卡间的通讯不能过滤（除非它要跨子网）。可以同时部署防火墙和安全组实现双重防护。</p></li><li><p>分布式路由</p><p> 为了降低网络节点的负载，同时提高可扩展性，OpenStack 自 Juno 版本开始正式引入了分布式路由（Distributed Virtual Router，DVR）特性（用户可以选择使用与否），来让计算节点自己来处理原先的大量东西向流量和非 SNAT 南北流量（有 floating IP 的 vm 跟外面的通信）。这样网络节点只需要处理占到一部分的 SNAT （无 floating IP 的 vm 跟外面的通信）流量，大大降低了负载和整个系统对网络节点的依赖。很自然的，FWaaS 也可以跟着放到计算节点上。DHCP 服务、VPN 服务目前仍然需要集中在网络节点上进行。</p></li></ol><h2 id="3-虚拟网络安装配置"><a href="#3-虚拟网络安装配置" class="headerlink" title="3 虚拟网络安装配置"></a>3 虚拟网络安装配置</h2><h3 id="3-1-部署前的准备"><a href="#3-1-部署前的准备" class="headerlink" title="3.1 部署前的准备"></a>3.1 部署前的准备</h3><p>挂载本地镜像源，安装以下 rpm 包：<code>python3-openstackclient</code>、<code>mariadb</code>、<code>mariadb-server</code>、<code>python3-PyMySQL</code>、<code>rabbitmq-server</code>、<code>memcached</code>、<code>python3-memcached</code>、<code>openstack-keystone</code>、<code>httpd</code>、<code>python3-mod_wsgi</code>、<code>openstack-neutron</code>、<code>openstack-neutron-ml2</code>、<code>openstack-neutron-openvswitch</code>、<code>ebtables</code>、<code>ipset</code>。</p><h3 id="3-2-Neutron-的脚本部署"><a href="#3-2-Neutron-的脚本部署" class="headerlink" title="3.2 Neutron 的脚本部署"></a>3.2 Neutron 的脚本部署</h3><ol><li><p>定义 IP 和主机名变量，后续部署使用</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">LOCALMANAGEMENTIP=<span class="string">&quot;192.168.230.134&quot;</span></span><br><span class="line">LOCALOVERLAYIP=<span class="string">&quot;192.168.230.134&quot;</span></span><br><span class="line">LOCALHOSTNAME=<span class="string">&quot;localhost.localdomain&quot;</span></span><br></pre></td></tr></table></figure></li><li><p>设置语言为中文、编码为 <code>UTF-8</code></p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">localectl set-locale LANG=zh_CN.UTF-8</span><br></pre></td></tr></table></figure></li><li><p>利用 <code>firewalld</code> 开放端口，更新防火墙规则</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">firewall-cmd --permanent --add-port 9696/tcp <span class="comment">#neutron</span></span><br><span class="line">firewall-cmd --permanent --add-port 5000/tcp <span class="comment">#keystone</span></span><br><span class="line">firewall-cmd --permanent --add-port 5672/tcp <span class="comment">#rabbitmq</span></span><br><span class="line">firewall-cmd --permanent --add-port 4789/udp <span class="comment">#vxlan</span></span><br><span class="line">firewall-cmd --permanent --add-port 11211/tcp <span class="comment">#memcached</span></span><br><span class="line">firewall-cmd --permanent --add-port 11211/udp <span class="comment">#memcached</span></span><br><span class="line">firewall-cmd --reload</span><br></pre></td></tr></table></figure></li><li><p>配置 MySQL</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &gt; /etc/my.cnf.d/openstack.cnf &lt;&lt; <span class="string">EOF</span></span><br><span class="line"><span class="string">[mysqld]</span></span><br><span class="line"><span class="string">bind-address = $LOCALMANAGEMENTIP</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">default-storage-engine = innodb</span></span><br><span class="line"><span class="string">innodb_file_per_table = on</span></span><br><span class="line"><span class="string">max_connections = 4096</span></span><br><span class="line"><span class="string">collation-server = utf8_general_ci</span></span><br><span class="line"><span class="string">character-set-server = utf8</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure></li><li><p>启动 <code>mariadb.service</code></p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl <span class="built_in">enable</span> mariadb.service</span><br><span class="line">systemctl start mariadb.service</span><br></pre></td></tr></table></figure></li><li><p>启动 <code>rabbitmqp-server</code>，增加新用户名和密码，给用户赋予配置、读、写权限</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">systemctl <span class="built_in">enable</span> rabbitmq-server.service</span><br><span class="line">systemctl start rabbitmq-server.service</span><br><span class="line">rabbitmqctl add_user openstack openstack</span><br><span class="line">rabbitmqctl set_permissions openstack <span class="string">&quot;.*&quot;</span> <span class="string">&quot;.*&quot;</span> <span class="string">&quot;.*&quot;</span></span><br><span class="line">systemctl restart rabbitmq-server.service</span><br></pre></td></tr></table></figure><blockquote><p>此处发生错误，在 <code>/etc/rabbitmq/rabbitmq-env.conf</code> 文件中添加 <code>NODENAME=rabbit@localhost</code>，重启 <code>rabbitmq-server</code> 解决。</p></blockquote></li><li><p>将 <code>memcached</code> 监听地址改为所有 IP，启动服务</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sed -i <span class="string">&#x27;/OPTIONS/c\OPTIONS=&quot;-l 0.0.0.0&quot;&#x27;</span> /etc/sysconfig/memcached</span><br><span class="line">systemctl <span class="built_in">enable</span> memcached.service</span><br><span class="line">systemctl start memcached.service</span><br></pre></td></tr></table></figure></li><li><p>MySQL 创建 <code>keystone</code> 和 <code>neutron</code> 数据库</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">mysql -u root -p &lt;&lt;<span class="string">EOF 2&gt;/dev/null</span></span><br><span class="line"><span class="string">    CREATE DATABASE keystone;</span></span><br><span class="line"><span class="string">    GRANT ALL PRIVILEGES ON keystone.* TO &#x27;keystone&#x27;@&#x27;localhost&#x27; IDENTIFIED BY &#x27;keystone&#x27;;</span></span><br><span class="line"><span class="string">    GRANT ALL PRIVILEGES ON keystone.* TO &#x27;keystone&#x27;@&#x27;%&#x27; IDENTIFIED BY &#x27;keystone&#x27;;</span></span><br><span class="line"><span class="string">    GRANT ALL PRIVILEGES ON keystone.* TO &#x27;keystone&#x27;@&#x27;$LOCALHOSTNAME&#x27; IDENTIFIED BY &#x27;keystone&#x27;;</span></span><br><span class="line"><span class="string">    CREATE DATABASE neutron;</span></span><br><span class="line"><span class="string">    GRANT ALL PRIVILEGES ON neutron.* TO &#x27;neutron&#x27;@&#x27;localhost&#x27; IDENTIFIED BY &#x27;neutron&#x27;;</span></span><br><span class="line"><span class="string">    GRANT ALL PRIVILEGES ON neutron.* TO &#x27;neutron&#x27;@&#x27;%&#x27; IDENTIFIED BY &#x27;neutron&#x27;;</span></span><br><span class="line"><span class="string">    GRANT ALL PRIVILEGES ON neutron.* TO &#x27;neutron&#x27;@&#x27;$LOCALHOSTNAME&#x27; IDENTIFIED BY &#x27;neutron&#x27;;</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure></li><li><p>更新 <code>keystone</code> 配置文件，设置文件所有权，设置管理 IP，初始化数据库信息和 <code>Fernet</code> 密钥数据库，并设置密码和节点</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf /etc/keystone/keystone.conf</span><br><span class="line"><span class="built_in">cp</span> ./keystone.conf /etc/keystone/keystone.conf</span><br><span class="line"><span class="built_in">chown</span> root:keystone /etc/keystone/keystone.conf</span><br><span class="line">sed -i <span class="string">&quot;s/172.30.20.211/<span class="variable">$&#123;LOCALMANAGEMENTIP&#125;</span>/g&quot;</span> /etc/keystone/keystone.conf</span><br><span class="line"></span><br><span class="line">su -s /bin/sh -c <span class="string">&quot;keystone-manage db_sync&quot;</span> keystone</span><br><span class="line">keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone</span><br><span class="line">keystone-manage credential_setup --keystone-user keystone --keystone-group keystone</span><br><span class="line">keystone-manage bootstrap --bootstrap-password admin \</span><br><span class="line">    --bootstrap-admin-url http://<span class="variable">$&#123;LOCALMANAGEMENTIP&#125;</span>:5000/v3/ \</span><br><span class="line">    --bootstrap-internal-url http://<span class="variable">$&#123;LOCALMANAGEMENTIP&#125;</span>:5000/v3/ \</span><br><span class="line">    --bootstrap-public-url http://<span class="variable">$&#123;LOCALMANAGEMENTIP&#125;</span>:5000/v3/ \</span><br><span class="line">    --bootstrap-region-id RegionOne</span><br></pre></td></tr></table></figure></li><li><p>设置管理 IP，配置 HTTP 配置文件，启动服务</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ln</span> -s /usr/share/keystone/wsgi-keystone.conf /etc/httpd/conf.d/</span><br><span class="line">systemctl <span class="built_in">enable</span> httpd.service</span><br><span class="line">systemctl start httpd.service</span><br></pre></td></tr></table></figure></li><li><p>配置用户名、密码、项目名等信息</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &gt;&gt; ~/.bashrc &lt;&lt; <span class="string">EOF</span></span><br><span class="line"><span class="string">export OS_USERNAME=admin</span></span><br><span class="line"><span class="string">export OS_PASSWORD=admin</span></span><br><span class="line"><span class="string">export OS_PROJECT_NAME=admin</span></span><br><span class="line"><span class="string">export OS_USER_DOMAIN_NAME=Default</span></span><br><span class="line"><span class="string">export OS_PROJECT_DOMAIN_NAME=Default</span></span><br><span class="line"><span class="string">export OS_AUTH_URL=http://$&#123;LOCALMANAGEMENTIP&#125;:5000/v3</span></span><br><span class="line"><span class="string">export OS_IDENTITY_API_VERSION=3</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure></li><li><p>获取 openstack token 并创建 service 项目</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> ~/.bashrc</span><br><span class="line">openstack token issue <span class="comment">#获取token</span></span><br><span class="line">openstack project create --domain default --description <span class="string">&quot;Service Project&quot;</span> service <span class="comment">#创建service项目</span></span><br></pre></td></tr></table></figure></li><li><p>创建 neutron 用户，将 neutron 用户添加入 service 项目并拥有 admin 权限，创建 network 服务并添加三个 endpoint</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">openstack user create --domain default --password neutron neutron</span><br><span class="line">openstack role add --project service --user neutron admin</span><br><span class="line">openstack service create --name neutron --description <span class="string">&quot;OpenStack Networking&quot;</span> network</span><br><span class="line">openstack endpoint create --region RegionOne network public http://<span class="variable">$&#123;LOCALMANAGEMENTIP&#125;</span>:9696</span><br><span class="line">openstack endpoint create --region RegionOne network internal http://<span class="variable">$&#123;LOCALMANAGEMENTIP&#125;</span>:9696</span><br><span class="line">openstack endpoint create --region RegionOne network admin http://<span class="variable">$&#123;LOCALMANAGEMENTIP&#125;</span>:9696</span><br></pre></td></tr></table></figure></li><li><p>更新 neutron 配置文件，设置文件所有权和管理 IP</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf /etc/neutron/neutron.conf</span><br><span class="line"><span class="built_in">cp</span> ./neutron.conf /etc/neutron/neutron.conf</span><br><span class="line"><span class="built_in">chown</span> root:neutron /etc/neutron/neutron.conf</span><br><span class="line">sed -i <span class="string">&quot;s/172.30.20.211/<span class="variable">$&#123;LOCALMANAGEMENTIP&#125;</span>/g&quot;</span> /etc/neutron/neutron.conf</span><br></pre></td></tr></table></figure></li><li><p>更新 ml2 配置文件和所有权</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf /etc/neutron/plugins/ml2/ml2_conf.ini</span><br><span class="line"><span class="built_in">cp</span> ./ml2_conf.ini /etc/neutron/plugins/ml2/ml2_conf.ini</span><br><span class="line"><span class="built_in">chown</span> root:neutron /etc/neutron/plugins/ml2/ml2_conf.ini</span><br></pre></td></tr></table></figure></li><li><p>更新 openvswitch_agent 配置文件、所有权、业务 IP</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf /etc/neutron/plugins/ml2/openvswitch_agent.ini</span><br><span class="line"><span class="built_in">cp</span> ./openvswitch_agent.ini /etc/neutron/plugins/ml2/openvswitch_agent.ini</span><br><span class="line"><span class="built_in">chown</span> root:neutron /etc/neutron/plugins/ml2/openvswitch_agent.ini</span><br><span class="line">sed -i <span class="string">&quot;s/172.30.20.211/<span class="variable">$&#123;LOCALOVERLAYIP&#125;</span>/g&quot;</span> /etc/neutron/plugins/ml2/openvswitch_agent.ini</span><br></pre></td></tr></table></figure></li><li><p>更新 l3_agent 配置文件和所有权</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf /etc/neutron/l3_agent.ini</span><br><span class="line"><span class="built_in">cp</span> ./l3_agent.ini /etc/neutron/l3_agent.ini</span><br><span class="line"><span class="built_in">chown</span> root:neutron /etc/neutron/l3_agent.ini</span><br></pre></td></tr></table></figure></li><li><p>更新 dhcp_agent 配置文件和所有权，杀死 dnsmasq 进程</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf /etc/neutron/dhcp_agent.ini</span><br><span class="line"><span class="built_in">cp</span> ./dhcp_agent.ini /etc/neutron/dhcp_agent.ini</span><br><span class="line"><span class="built_in">chown</span> root:neutron /etc/neutron/dhcp_agent.ini</span><br><span class="line">pkill dnsmasq</span><br></pre></td></tr></table></figure></li><li><p>更新 ml2_conf 配置文件，同步数据库</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ln</span> -s /etc/neutron/plugins/ml2/ml2_conf.ini /etc/neutron/plugin.ini</span><br><span class="line">su -s /bin/sh -c <span class="string">&quot;neutron-db-manage --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugins/ml2/ml2_conf.ini upgrade head&quot;</span> neutron</span><br></pre></td></tr></table></figure></li><li><p>启动 neutron 服务</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl <span class="built_in">enable</span> neutron-server.service openvswitch.service neutron-dhcp-agent.service neutron-l3-agent.service neutron-openvswitch-agent.service</span><br><span class="line">systemctl start neutron-server.service openvswitch.service neutron-dhcp-agent.service neutron-l3-agent.service neutron-openvswitch-agent.service</span><br></pre></td></tr></table></figure></li></ol><h3 id="3-3-验证网络操作"><a href="#3-3-验证网络操作" class="headerlink" title="3.3 验证网络操作"></a>3.3 验证网络操作</h3><p>使用 <code>openstack network agent list</code> 命令列出代理以验证启动 neutron 代理是否成功，得到输出如下：</p><p><img data-src="https://i0.hdslb.com/bfs/album/b0e9f005f1d5fc7934ac3168abe611220011e200.png" alt="agent-list"></p><p>启动成功。</p><h3 id="3-4-Neutron-创建网络和子网"><a href="#3-4-Neutron-创建网络和子网" class="headerlink" title="3.4 Neutron 创建网络和子网"></a>3.4 Neutron 创建网络和子网</h3><p>使用 <code>neutron net-create net1</code> 命令创建名为 <code>net1</code> 的网络：</p><img data-src="https://i0.hdslb.com/bfs/album/cf1a0915905448b5a2d9f0518b6a61f0ac055aaa.png" alt="net-create" style="zoom:50%;" /><p>使用 <code>neutron subnet-create net1 192.168.2.0/24 --name subnet1</code> 命令创建名为 <code>subnet1</code>、IP 为 <code>192.168.0.2/24</code> 的子网：</p><img data-src="https://i0.hdslb.com/bfs/album/add3cc6aed1c990d4d4ddb88fb043ae1cc7eaffc.png" alt="subnet-create" style="zoom:50%;" /><p>查看创建结果：</p><p><img data-src="https://i0.hdslb.com/bfs/album/29adb9796e0de81a332335d37f40cb8b53955536.png" alt="create-result"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;简单记录一下一个多月的“实训”。&lt;/p&gt;</summary>
    
    
    
    <category term="笔记" scheme="https://superpung.com/note/"/>
    
    
    <category term="Linux" scheme="https://superpung.com/tags/Linux/"/>
    
    <category term="计算机网络" scheme="https://superpung.com/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>《软件测试技术》笔记</title>
    <link href="https://superpung.com/stt-notes/"/>
    <id>https://superpung.com/stt-notes/</id>
    <published>2022-05-26T10:18:30.000Z</published>
    <updated>2022-05-26T10:18:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>《软件测试技术》</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;《软件测试技术》&lt;/p&gt;
</summary>
      
    
    
    
    <category term="笔记" scheme="https://superpung.com/note/"/>
    
    
    <category term="软件测试" scheme="https://superpung.com/tags/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/"/>
    
  </entry>
  
  <entry>
    <title>Q-Learning 简介</title>
    <link href="https://superpung.com/q-learning/"/>
    <id>https://superpung.com/q-learning/</id>
    <published>2022-04-05T02:50:56.000Z</published>
    <updated>2022-04-05T02:50:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>强化学习（Reinforcement Learning）是一种用于模拟现实世界的算法，而其中的一个重要的组成部分是 Q-Learning。</p><span id="more"></span><p>假设有一系列「状态」（state）组成的环境，在每一个状态中都可以采取一种「动作」（action）到达下一个状态。 这些状态并不都是相同的，根据实际应用，不同的状态有不同的「奖励」（reward），而且存在终止状态。我们的目标是从任意初始状态出发，到达终止状态，且所获得的奖励最大。</p><p>Q-Learning 就是这样一种决策算法，帮助我们在每个状态决定应该采取何种动作，才能最终得到最大奖励。</p><h2 id="算法描述"><a href="#算法描述" class="headerlink" title="算法描述"></a>算法描述</h2><p>Q-Learning 算法的核心是维护一张 Q 表，它的每一行对应一个状态，每一列对应一种动作，每个值代表了这个状态下采取这种动作最终可以得到的最大奖励。Q-Learning 会经过多次尝试去更新其中的值，不断试错，从而“学习”到外部环境的机制。</p><p>步骤如下：</p><ol><li>随机初始化 $Q(s, a)$</li><li>重复以下步骤，每一次重复记作一次 episode：<ol><li>初始化状态 s</li><li>重复以下步骤，每一次重复记作一次 episode 的一步（step）：<ol><li>选择状态 s 下要采取的动作 a（依据一定的策略，如 $\epsilon$-greedy）</li><li>执行动作 a，得到奖励 r，到达下一状态 s’</li><li>更新 Q 表：$Q(s,a)\leftarrow Q(s,a)+\alpha[r+\gamma max_{a’}Q(s’,a’)-Q(s,a)]$</li><li>更新状态：$s\leftarrow s’$</li></ol></li><li>当 s 为终止状态，停止</li></ol></li></ol><p>要说明的：</p><ul><li>在每个 step 中，要首先根据一定策略选择要执行的动作。其中 $\epsilon$-greedy 策略是在一定概率下按 Q 表的最优值选择动作，其他情况下随机选择动作。</li><li>在更新 Q 表的步骤中，$r+\gamma max_{a’}Q(s’,a’)$ 是当前得到的最大奖励值（当前状态 s 采取动作 a 获得的奖励 + 下一状态 s’ 获得的最大奖励（Q 表预测的 ）），而 $Q(s,a)$ 则是 Q 表预测的最大奖励值。将二者作差并乘上一个学习率 $\alpha$，叠加到现有的 Q 表中，就完成了 Q 表的一次更新。</li><li>上面中出现的 $\gamma$ 是衰减率，为 0 即不考虑未来奖励如何，只考虑当前奖励最大；为 1 即考虑所有未来的奖励；介于 0 和 1 之间则考虑的程度随 $\gamma^2$ 不断衰减。</li></ul><h2 id="代码演示"><a href="#代码演示" class="headerlink" title="代码演示"></a>代码演示</h2><p>根据上面数学角度的分析，不难看出实际需要处理的就是两个部分：选择动作和更新 Q 表。下面用 <code>python</code> 演示这两部分。</p><h3 id="init"><a href="#init" class="headerlink" title="__init__"></a><code>__init__</code></h3><p>初始化函数主要为建立 Q 表：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, actions, learning_rate=<span class="number">0.01</span>, reward_decay=<span class="number">0.9</span>, e_greedy=<span class="number">0.9</span></span>):</span><br><span class="line">    <span class="variable language_">self</span>.actions = actions</span><br><span class="line">    <span class="variable language_">self</span>.lr = learning_rate</span><br><span class="line">    <span class="variable language_">self</span>.gamma = reward_decay</span><br><span class="line">    <span class="variable language_">self</span>.epsilon = e_greedy</span><br><span class="line">    <span class="variable language_">self</span>.q_table = pd.DataFrame(columns=<span class="variable language_">self</span>.actions, dtype=np.float64)</span><br></pre></td></tr></table></figure><p>其中 <code>actions</code> 为动作的集合（<code>list</code> 类型），需要用到 <code>pandas</code> 和 <code>numpy</code> 库。</p><h3 id="choose-action"><a href="#choose-action" class="headerlink" title="choose_action"></a><code>choose_action</code></h3><p><code>choose_action</code> 是选择动作的函数，需要接收一个参数作为当前状态：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">choose_action</span>(<span class="params">self, observation</span>):</span><br></pre></td></tr></table></figure><p>为方便后续处理和操作，将参数转换为 <code>str</code> 类型：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">observation = <span class="built_in">str</span>(observation)</span><br></pre></td></tr></table></figure><p>接下来需要选择要执行的动作了，但我们首先要判断参数对应的状态是否在 Q 表中，需要引入函数 <code>check_state_exist</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">check_state_exist</span>(<span class="params">self, state</span>):</span><br><span class="line">    <span class="keyword">if</span> state <span class="keyword">not</span> <span class="keyword">in</span> <span class="variable language_">self</span>.q_table.index:</span><br><span class="line">        <span class="comment"># append new state to q table</span></span><br><span class="line">        <span class="variable language_">self</span>.q_table = <span class="variable language_">self</span>.q_table.append(</span><br><span class="line">            pd.Series(</span><br><span class="line">                [<span class="number">0</span>] * <span class="built_in">len</span>(<span class="variable language_">self</span>.actions),</span><br><span class="line">                index=<span class="variable language_">self</span>.q_table.columns,</span><br><span class="line">                name=state,</span><br><span class="line">            )</span><br><span class="line">        )</span><br></pre></td></tr></table></figure><p>逻辑很简单，当这个状态不在 Q 表中时，我们就把它加入 Q 表。</p><div class="note warning"><p>若此处出现报错 <code>TypeError: unhashable type: &#39;list&#39;</code>，应该是把 <code>list</code> 类型的 <code>state</code> 作为参数传了过来。<code>list</code> 类型是不可以作为 key 的，注意需要转换为 <code>str</code> 类型。</p></div><p>在 <code>choose_action</code> 中调用函数 <code>check_state_exist</code> 进行判断：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">self</span>.check_state_exist(observation)</span><br></pre></td></tr></table></figure><p>接下来就可以进行动作的选择了！</p><p>注意之前提到的 $\epsilon$-greedy 策略，我们需要在一定的概率下选择 Q 表中最优值：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> np.random.uniform() &lt; <span class="variable language_">self</span>.epsilon:</span><br><span class="line">    <span class="comment"># choose best action</span></span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="comment"># choose random action</span></span><br></pre></td></tr></table></figure><p>回忆一下，Q 表每一行对应一个状态、每一列对应一种动作。所以在 Q 表中选择动作，首先要找到当前的状态：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">state_action = <span class="variable language_">self</span>.q_table.loc[observation, :]</span><br></pre></td></tr></table></figure><p>然后就可以找这一行中的最大值对应的动作了。</p><p>但是这里有一个问题，如果有多个相同的最大值呢？这种情况下如果我们直接判断 argmax，返回的将永远是第一个最大值，永远不会遍历到后面的最大值对应的动作。所以这里我们需要随机选择一个最大值来避免这种情况：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">action = np.random.choice(state_action[state_action == state_action.<span class="built_in">max</span>()].index)</span><br></pre></td></tr></table></figure><p>这就是基于 Q 表选择的动作了。</p><p>在另一个分支进行随机选择：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">action = np.random.choice(<span class="variable language_">self</span>.actions)</span><br></pre></td></tr></table></figure><p>最后返回执行的动作：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> action</span><br></pre></td></tr></table></figure><p>完整代码：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">choose_action</span>(<span class="params">self, observation</span>):</span><br><span class="line">    observation = <span class="built_in">str</span>(observation)</span><br><span class="line">    <span class="variable language_">self</span>.check_state_exist(observation)</span><br><span class="line">    <span class="comment"># action selection</span></span><br><span class="line">    <span class="keyword">if</span> np.random.uniform() &lt; <span class="variable language_">self</span>.epsilon:</span><br><span class="line">        <span class="comment"># choose best action</span></span><br><span class="line">        state_action = <span class="variable language_">self</span>.q_table.loc[observation, :]</span><br><span class="line">        action = np.random.choice(state_action[state_action == state_action.<span class="built_in">max</span>()].index)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="comment"># choose random action</span></span><br><span class="line">        action = np.random.choice(<span class="variable language_">self</span>.actions)</span><br><span class="line">        <span class="keyword">return</span> action</span><br></pre></td></tr></table></figure><h3 id="learn"><a href="#learn" class="headerlink" title="learn"></a><code>learn</code></h3><p><code>learn</code> 是学习的过程，也就是更新 Q 表的函数，需要接收当前状态、当前执行的动作、获得的奖励和下一个状态 4 个参数：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">learn</span>(<span class="params">self, s, a, r, s_</span>):</span><br></pre></td></tr></table></figure><p>同样对状态做字符串化处理，并判断下一个状态是否在 Q 表中：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">s = <span class="built_in">str</span>(s)</span><br><span class="line">s_ = <span class="built_in">str</span>(s_)</span><br><span class="line"><span class="variable language_">self</span>.check_state_exist(s_)</span><br></pre></td></tr></table></figure><p>根据上一部分的分析，更新 Q 表需要知道当前选择对应的最大奖励值（$r+\gamma max_{a’}Q(s’,a’)$，记作 <code>q_target</code>）和 Q 表预测的最大奖励值（$Q(s,a)$，记作 <code>q_predict</code>）。</p><p>其中 <code>q_predict</code> 比较容易得到：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">q_predict = <span class="variable language_">self</span>.q_table.loc[s, a]</span><br></pre></td></tr></table></figure><p>而 <code>q_target</code> 需要判断下一状态是否到了最终状态，如果是，则 <code>q_target</code> 直接为当前获得的奖励 <code>r</code>，否则根据公式计算：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> s_ != <span class="string">&#x27;terminal&#x27;</span>:</span><br><span class="line">    q_target = r + <span class="variable language_">self</span>.gamma * <span class="variable language_">self</span>.q_table.loc[s_, :].<span class="built_in">max</span>()  <span class="comment"># next state is not terminal</span></span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    q_target = r  <span class="comment"># next state is terminal</span></span><br></pre></td></tr></table></figure><p>更新 Q 表：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">self</span>.q_table.loc[s, a] += <span class="variable language_">self</span>.lr * (q_target - q_predict)</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://mofanpy.com/tutorials/machine-learning/reinforcement-learning/">强化学习 (Reinforcement Learning) | 莫烦Python</a></p><h2 id="鸣谢"><a href="#鸣谢" class="headerlink" title="鸣谢"></a>鸣谢</h2><p><a href="https://copilot.github.com/">GitHub Copilot · Your AI pair programmer</a></p><p><img data-src="https://i0.hdslb.com/bfs/album/149d09fab5e7742af1c45be2ef804110282b2bcb.jpg" alt="copilot-qlearning"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;强化学习（Reinforcement Learning）是一种用于模拟现实世界的算法，而其中的一个重要的组成部分是 Q-Learning。&lt;/p&gt;</summary>
    
    
    
    <category term="技术" scheme="https://superpung.com/tech/"/>
    
    
    <category term="机器学习" scheme="https://superpung.com/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="强化学习" scheme="https://superpung.com/tags/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="Q-Learning" scheme="https://superpung.com/tags/Q-Learning/"/>
    
  </entry>
  
  <entry>
    <title>软件测试技术实验 3 和 4 遇到的一些问题和解决方案</title>
    <link href="https://superpung.com/software-testing-lab3-4/"/>
    <id>https://superpung.com/software-testing-lab3-4/</id>
    <published>2022-03-23T14:02:56.000Z</published>
    <updated>2022-03-23T14:02:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近写《软件测试技术》实验 3 和实验 4 时，遇到了一些工具上的问题，并尝试了一些解决方案，记录一下。</p><span id="more"></span><h1 id="实验-3：MuJava"><a href="#实验-3：MuJava" class="headerlink" title="实验 3：MuJava"></a>实验 3：MuJava</h1><p><a href="https://cs.gmu.edu/~offutt/mujava/">MuJava</a> 是一个变异测试的工具，可以生成变异体并运行，最终可以得到变异测试的结果。</p><blockquote><p>变异测试是衡量测试充分性的有效方法，其思想就是模拟程序编写中可能会出现的错误，得到一系列和源程序不同的“变异体”，用来“测试”你的测试程序是否能测出来这些“变异”。你的测试程序杀死（使变异体的运行结果不同于源程序）的变异体越多，测试越充分。</p></blockquote><h2 id="0x00-安装-MuJava"><a href="#0x00-安装-MuJava" class="headerlink" title="0x00 安装 MuJava"></a>0x00 安装 MuJava</h2><ol><li><p>直接到 <a href="https://cs.gmu.edu/~offutt/mujava/">官网</a> 下载 <code>mujava.jar</code>。</p></li><li><p>为了后续实验进行，还需要下载 <code>junit.jar</code>（编写测试程序）、<code>openjava.jar</code>。</p></li><li><p>保存三个 jar 包到固定路径，编辑 <code>~/.bash_profile</code> 引入环境：</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_281.jdk/Contents/Home</span><br><span class="line">export PATH=$PATH:$JAVA_HOME/bin</span><br><span class="line">export CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:/path/to/openjava.jar:/path/to/junit.jar:/path/to/mujava.jar</span><br></pre></td></tr></table></figure><p> （适用于 macOS，Windows 略有不同）</p></li><li><p>保存并运行 <code>source ~/.bash_profile</code>。</p></li><li><p>为了使用 MuJava，需要创建需要的目录结构：</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">|--\classes\</span><br><span class="line">|--mujava.config</span><br><span class="line">|--\result\</span><br><span class="line">|--\src\</span><br><span class="line">|--\testset\</span><br></pre></td></tr></table></figure></li></ol><h2 id="0x01-生成变异体"><a href="#0x01-生成变异体" class="headerlink" title="0x01 生成变异体"></a>0x01 生成变异体</h2><ol><li>将源文件移至 <code>\src\</code> 目录。</li><li><code>javac</code> 编译源文件，得到 <code>classes</code> 文件，移至 <code>\classes\</code> 目录。</li><li>运行 <code>java mujava.gui.GenMutantsMain</code>，进入图形化设置界面，勾选需要生成变异体的文件和需要的变异算子，点击 <code>Generate</code> 即可生成（保存在 <code>result</code> 目录）。</li></ol><div class="note warning"><p>若此处出现问题，请检查 0x00 第 3 步中路径是否包含空格。即使使用 <code>\</code> 转译也可能存在问题，建议用 <code>-</code> 或 <code>_</code> 代替空格。</p></div><h2 id="0x02-生成测试集"><a href="#0x02-生成测试集" class="headerlink" title="0x02 生成测试集"></a>0x02 生成测试集</h2><p>使用 JUnit 编写即可，比较简单，得到 <code>xxxTest.java</code>。</p><h2 id="0x03-运行变异测试"><a href="#0x03-运行变异测试" class="headerlink" title="0x03 运行变异测试"></a>0x03 运行变异测试</h2><ol><li><code>javac</code> 编译测试文件，得到 <code>classes</code> 文件，移至 <code>\testset\</code> 目录。</li><li>运行 <code>java mujava.gui.RunTestMain &gt; TestResult.log</code>，将变异测试日志输出至文件保存。</li></ol><div class="note warning"><p>若此处出现问题，请检查 0x00 第 3 步中是否引入了 <code>$JAVA_HOME/lib/tools.jar</code> 和 <code>$JAVA_HOME/lib/dt.jar</code>。</p></div><p>变异测试完成，后续可以进一步分析测试代码和变异体，做进一步改进。</p><h1 id="实验-4：Major"><a href="#实验-4：Major" class="headerlink" title="实验 4：Major"></a>实验 4：Major</h1><p><a href="http://mutation-testing.org/">Major</a> 和 Mujava 功能相近，也是变异测试的工具。</p><h2 id="0x00-安装-Major"><a href="#0x00-安装-Major" class="headerlink" title="0x00 安装 Major"></a>0x00 安装 Major</h2><ol><li><p>直接到 <a href="http://mutation-testing.org/downloads/">官网</a> 下载 Major v1.3.5，解压为 major 目录，存放到固定路径。</p></li><li><p>配置环境变量：</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export PATH=/path/to/major/bin:$PATH:$JAVA_HOME/bin</span><br></pre></td></tr></table></figure><p> 保存并运行 <code>source ~/.bash_profile</code>。</p></li><li><p>验证是否配置成功：</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">❯ javac -version</span><br><span class="line">javac 1.7.0-Major-v1.3.5</span><br><span class="line">❯ ant -version</span><br><span class="line">Apache Ant(TM) version 1.8.4-Major-v1.3.5 compiled on July 18 2019</span><br></pre></td></tr></table></figure></li></ol><div class="note warning"><p>如果验证时得到的输出和上述不同，特别是 <code>javac -version</code> 输出结果不包含 <code>Major-v1.3.5</code>，建议检查第 2 步引入环境变量的顺序，应将 <code>major/bin</code> 目录放在最前面。</p></div><h2 id="0x01-配置文件"><a href="#0x01-配置文件" class="headerlink" title="0x01 配置文件"></a>0x01 配置文件</h2><ol><li><p>Major 使用 <code>ant</code> 运行变异测试，并在 <code>major/example/</code> 目录下给出运行模板。所以我们需要依此建立目录结构：</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">|--\src\</span><br><span class="line">|--\test\</span><br><span class="line">|--build.xml</span><br><span class="line">|--run.sh</span><br></pre></td></tr></table></figure></li><li><p>将源文件移至 <code>src</code> 目录，将 <code>JUnit</code> 测试文件移至 <code>test</code> 目录。</p></li></ol><div class="note warning"><p>请确保代码文件的 <code>package</code> 和目录结构对应，参照给出的运行模板。</p></div><p><code>build.xml</code> 和 <code>run.sh</code> 是 Major 运行的重要文件，前者是配置文件、后者是运行脚本。由于运行目录不同，所以在运行前我们需要在 <code>example</code> 的基础上，根据自己的实际情况对两个文件做一些修改。</p><h3 id="build-xml"><a href="#build-xml" class="headerlink" title="build.xml"></a><code>build.xml</code></h3><ol><li><p>可以在第一行修改自己的项目名称：</p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">name</span>=<span class="string">&quot;YourProjectName&quot;</span> <span class="attr">default</span>=<span class="string">&quot;compile&quot;</span> <span class="attr">basedir</span>=<span class="string">&quot;.&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>修改 <code>/javac/</code> 文件的路径：</p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;major&quot;</span> <span class="attr">value</span>=<span class="string">&quot;/your/path/to/major/bin/javac&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p> 它上面两行一般不需要改动。</p></li></ol><h3 id="run-sh"><a href="#run-sh" class="headerlink" title="run.sh"></a><code>run.sh</code></h3><ol><li><p>修改 <code>/major/</code> 目录的路径：</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MAJOR_HOME=&quot;/your/path/to/major&quot;</span><br></pre></td></tr></table></figure></li><li><p>如果没有编写 MML 脚本（一般情况下不需要自己编写），修改 <code>ant</code> 运行的参数：</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$</span><span class="language-bash">MAJOR_HOME/bin/ant -DmutOp=<span class="string">&quot;:ALL&quot;</span> clean compile</span></span><br></pre></td></tr></table></figure><p> 直接指定为 <code>ALL</code> 对应全部变异算子，若此处不修改可能会导致后续生成变异体数量为 0。</p></li></ol><h2 id="0x02-运行变异测试"><a href="#0x02-运行变异测试" class="headerlink" title="0x02 运行变异测试"></a>0x02 运行变异测试</h2><p>运行 <code>./run.sh</code>，得到类似下方的输出则成功：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">mutation.test:</span><br><span class="line">     [echo] Running mutation analysis ...</span><br><span class="line">    [junit] MAJOR: Mutation analysis enabled</span><br><span class="line">    [junit] MAJOR: ------------------------------------------------------------</span><br><span class="line">    [junit] MAJOR: Run 1 ordered test to verify independence</span><br><span class="line">    [junit] MAJOR: ------------------------------------------------------------</span><br><span class="line">    [junit] MAJOR: Preprocessing time: xxx seconds</span><br><span class="line">    [junit] MAJOR: ------------------------------------------------------------</span><br><span class="line">    [junit] MAJOR: Mutants generated: xxx</span><br><span class="line">    [junit] MAJOR: Mutants covered:   xxx (xx.xx%)</span><br><span class="line">    [junit] MAJOR: ------------------------------------------------------------</span><br><span class="line">    [junit] MAJOR: Export test map to testMap.csv</span><br><span class="line">    [junit] MAJOR: ------------------------------------------------------------</span><br><span class="line">    [junit] MAJOR: Run mutation analysis with 1 individual test</span><br><span class="line">    [junit] MAJOR: ------------------------------------------------------------</span><br><span class="line">    [junit] MAJOR: 1/1 - UpgradedTriangleTest (xxms / xxx):</span><br><span class="line">    [junit] MAJOR: xxx (xxx / xxx / xxx) -&gt; AVG-RTPM: xms</span><br><span class="line">    [junit] MAJOR: Mutants killed / live: xxx (xxx-x-x) / xx</span><br><span class="line">    [junit] MAJOR: ------------------------------------------------------------</span><br><span class="line">    [junit] MAJOR: Summary:</span><br><span class="line">    [junit] MAJOR:</span><br><span class="line">    [junit] MAJOR: Analysis time:  x.x seconds</span><br><span class="line">    [junit] MAJOR: Mutation score: xx.xx% (xx.xx%)</span><br><span class="line">    [junit] MAJOR: Mutants killed / live: xxx (xxx-x-x) / xx</span><br><span class="line">    [junit] MAJOR: Mutant executions: xxx</span><br><span class="line">    [junit] MAJOR: ------------------------------------------------------------</span><br><span class="line">    [junit] MAJOR: Export summary of results to summary.csv</span><br><span class="line">    [junit] MAJOR: Export run-time results to results.csv</span><br><span class="line">    [junit] MAJOR: Export mutant kill details to killed.csv</span><br><span class="line"></span><br><span class="line">BUILD SUCCESSFUL</span><br><span class="line">Total time: x second</span><br></pre></td></tr></table></figure><p>和 MuJava 类似，后续可以进一步分析测试代码和变异体，做进一步改进。</p><div class="note warning"><p>如果 Mutants covered 未达到 100%，请移除源程序的 <code>main</code> 方法。</p></div><h2 id="0x03-其他问题"><a href="#0x03-其他问题" class="headerlink" title="0x03 其他问题"></a>0x03 其他问题</h2><p>如有其他问题，强烈建议阅读 <a href="http://mutation-testing.org/doc/major.pdf">官方文档</a>，十分详细。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近写《软件测试技术》实验 3 和实验 4 时，遇到了一些工具上的问题，并尝试了一些解决方案，记录一下。&lt;/p&gt;</summary>
    
    
    
    <category term="技术" scheme="https://superpung.com/tech/"/>
    
    
    <category term="Java" scheme="https://superpung.com/tags/Java/"/>
    
    <category term="软件测试" scheme="https://superpung.com/tags/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/"/>
    
  </entry>
  
  <entry>
    <title>我和我的 2021</title>
    <link href="https://superpung.com/rev-2021/"/>
    <id>https://superpung.com/rev-2021/</id>
    <published>2021-12-31T03:07:41.000Z</published>
    <updated>2021-12-31T03:07:41.000Z</updated>
    
    <content type="html"><![CDATA[<p>用数字记录 2021 很容易，但用文字记录却很难。</p><span id="more"></span><p>每一天都看似平常，但 365 个平常交叠，总会带来不一样的感受。是啊，茫茫中又度过了一年。2020 的记忆未曾消去，2021 却已悄然而过，来不及沉浸在过去的回忆里，时间催促着我们向前看，2022 的曙光即将在黑夜之后到来。</p><p>每逢新年伊始，心中满怀对新年的企盼。无数次盼望新年胜旧年，盼望旧年的伤悲都能随风，盼望新年的喜乐都能常伴。心怀希望永远是我们面对陌生的勇气，即使时代并不会对我们好些，我们也会继续走下去。</p><p>即使新冠疫情改变了世界的运作方式，但 2021 对我来说也不算很糟。做出了很多新的尝试，有很多有意义的回忆，并没有停留在以前的认知。热爱着我所热爱的，坚持着我所坚持的，继续学习，继续进步。</p><p>这一年的课余时间除了休息外，应该只有做一些项目和学一些新的知识了。开始使用 Notion 作为笔记管理工具，所以很惭愧没有经常更新博客……（2022 一定会多更新的，不能荒废了）学习真是一件容易让人满足的事情。</p><hr><p>找到几张比较有意义的照片，不能说可以贯穿 2021，但也能在漫无目的中留下一些意义。</p><img data-src="https://i0.hdslb.com/bfs/album/bbc36bcb82c4ec376b7879dc2c527e1d35ae98fb.png" alt="2021-1" style="zoom:50%;" /><p>2021 收到最有心的生日礼物，感谢一直陪伴我的 peggy。</p><img data-src="https://i0.hdslb.com/bfs/album/de7fb44cd42d755fe1fc6b5c18ba31bfaabbec60.png" alt="2021-2" style="zoom:50%;" /><p>在天津大悦城 Apple Store 第一次体验了 iPhone 12 mini，可以说是理想中的手机大小了。</p><img data-src="https://i0.hdslb.com/bfs/album/120dd7cbc86c4a1f580d32100b0c77000b51ca7f.png" alt="2021-3" style="zoom:50%;" /><p>AirPods Pro 牛年限定……</p><img data-src="https://i0.hdslb.com/bfs/album/7c73478bbaaff06bb67182c71a46b0393439dda1.png" alt="2021-4" style="zoom:50%;" /><p>拥有了第一台 Mac（也是最贵的生日礼物……），陪伴我一年的 Hackintosh 终于光荣退役，让它重新回到了 Windows。</p><img data-src="https://i0.hdslb.com/bfs/album/a7458dd71e3c307a54af963e5c3220eda10cbcc3.png" alt="2021-5" style="zoom:50%;" /><p>第一次坐救护车……希望不再有这种经历了。</p><p><img data-src="https://i0.hdslb.com/bfs/album/5f437ac89fb86545f48e1bbf316c412d30b4411b.png" alt="2021-6"></p><p>青年湖畔的落日和晚霞。</p><p><img data-src="https://i0.hdslb.com/bfs/album/b39be791b02aba811e5a14b4f92012f5533824a5.png" alt="2021-8"></p><p>重回卫津路，19 年的回忆浮现</p><p><img data-src="https://i0.hdslb.com/bfs/album/7b72db40a286d42b5f256f7335348c178fe1965f.png" alt="2021-9"></p><p>打卡三里屯 Apple Store。</p><p><img data-src="https://i0.hdslb.com/bfs/album/653ef1b06ed1c52473c150aff2c2f3490d55a27b.png" alt="2021-10"></p><p>第一次听周鸿祎老板的讲座。</p><p><img data-src="https://i0.hdslb.com/bfs/album/9e105498cdbb06af51da59a27223d604c734f494.png" alt="2021-11"></p><p>第一次去海洋馆，看到了北极狐。</p><p><img data-src="https://i0.hdslb.com/bfs/album/119258435b314174e34ec9b00de092d83092bb99.png" alt="2021-13"></p><p>去看了世界智能大会。</p><img data-src="https://i0.hdslb.com/bfs/album/93d85534b12a95e9dff123c169dd88c5f3b33396.png" alt="2021-14" style="zoom:50%;" /><p>AirPods Pro，喧嚣离我远一步。</p><p><img data-src="https://i0.hdslb.com/bfs/album/fc60372f2604e915587f03a3725b78bb651d64d9.png" alt="2021-15"></p><p>青年湖畔的午后。</p><p><img data-src="https://i0.hdslb.com/bfs/album/0f998ce90b8b74a0c04e302f7f14fb8606f411ad.png" alt="2021-16"></p><p>青年湖畔的晚霞。</p><img data-src="https://i0.hdslb.com/bfs/album/d260f69b7f053c68649c1ab7ac8e70972898bbcc.png" alt="2021-17" style="zoom:50%;" /><p>可能……是继 4s 后的第一部小手机？</p><p><img data-src="https://i0.hdslb.com/bfs/album/405c22ac5a10bdd5710b95d7120b0d28c5957549.png" alt="2021-18"></p><p>夜景模式下的体育场。</p><p><img data-src="https://i0.hdslb.com/bfs/album/0c4178f4c27a733e24e7a0f6a4fbaeb9c2d18c1a.png" alt="2021-19"></p><p>2021 初雪，随立冬而至。前一天晚上还在下雨，第二天醒来室外已是银装素裹。</p><p><img data-src="https://i0.hdslb.com/bfs/album/57b39bdb596e6916b43ad4988339aec62deed91a.png" alt="2021-20"></p><p>打卡天津恒隆广场 Apple Store。</p><p><img data-src="https://i0.hdslb.com/bfs/album/c359533b63cfbceaaefb262a6770a817536aab97.png" alt="2021-21"></p><p>22 考研第一天傍晚，路上满是出校的车。</p><p><img data-src="https://i0.hdslb.com/bfs/album/94cef62384e8d5ec2029703d34ab92f7d59e63d6.png" alt="2021-24"></p><p><img data-src="https://i0.hdslb.com/bfs/album/dc3d493bc67161ae50101da2e6a81a5470b5fe72.png" alt="2021-22"></p><p>每一年都和网易云度过，今年比去年多听了近 200 小时、近 1000 首歌。</p><img data-src="https://i0.hdslb.com/bfs/album/ed49fe23ded6dd54d3149c7e11452f9b4c701f9b.png" alt="2021-23" style="zoom:50%;" /><p>没想到今年有 324 天都登录了 B 站 🤣</p><hr><p>照片可以定格时间，时间也让照片有了岁月的意义。回望 2021 迈出的每一步，似乎都是平平无奇的一个念头，但这些「抉择」确确实实改变了许多。「落子无悔，抉择本身就是向前」，希望自己可以摆脱抱怨，坚持自己的选择，永远向前看。</p><p>来不及停留，来不及等待。来不及感伤岁月，来不及蹉跎光阴。惟愿 2022 的每一分时光都有意义，继续热爱生活。</p><p><img data-src="https://i0.hdslb.com/bfs/album/f47dfd5c157f553f495dd7ff24483b597c9995eb.png" alt="2021-25"></p><p>2021，再见！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;用数字记录 2021 很容易，但用文字记录却很难。&lt;/p&gt;</summary>
    
    
    
    <category term="生活" scheme="https://superpung.com/life/"/>
    
    
  </entry>
  
  <entry>
    <title>写过的第二个 App</title>
    <link href="https://superpung.com/my-2nd-app/"/>
    <id>https://superpung.com/my-2nd-app/</id>
    <published>2021-09-14T14:34:21.000Z</published>
    <updated>2021-09-14T14:34:21.000Z</updated>
    
    <content type="html"><![CDATA[<p>可能是第一个比较成熟的 app。</p><span id="more"></span><h1 id="What-and-Why"><a href="#What-and-Why" class="headerlink" title="What and Why"></a>What and Why</h1><p>好久没有更新博客了。上一次更新还是在 7 月 13 日，正好过去了两个月的时间。上一篇文章是在学习 swiftUI 时记录的，如今已经完成了 <a href="https://github.com/SuperPung/HwS-100days">100 days of swiftUI</a>。写上一篇文章的时候还没开始做这个 App，如今已经结束了。</p><p>两个月的时间，从在家躺平到提前返校，再到开始小学期、开始暑期训练营，再到正式上课……与此同时，这个 App 的开发也正好伴随着我度过这两个月。</p><p>做的第一个 App 是在 2020 年的暑期训练营，是一个 Todo 的快应用。虽然获了奖，但确实太简单了。一个人完成的项目，并没有付出很大的精力。但是在今年的暑期训练营，深深感受到了 Android 原生开发的困难，可能这也是 Flutter 等框架兴起的原因吧。</p><p>言归正传，我的第二个 App 并不能称作“我的”，因为小组的产品和设计起了很大作用，没有她们的努力，这个 App 最终也不会呈现。特别感谢设计组的朋友，无论是界面、配色、交互等各个方面，都设计得很完美。也要感谢 iOS 组组长提供的学习机会，学到了很多。</p><p>App 的功能是记账，当这个需求发布的时候我正想找一个记账的软件，遂即开始开发。我试用了很多记账的 App，大多十分冗杂。最终我选择了 <a href="https://apps.apple.com/cn/app/icost-%E8%AE%B0%E8%B4%A6-%E5%BF%AB%E9%80%9F%E7%AE%80%E6%B4%81%E5%A5%BD%E7%94%A8%E7%9A%84%E7%90%86%E8%B4%A2%E5%8A%A9%E6%89%8B/id1484262528">iCost</a>，并且至今仍在使用。它界面的简介和纯粹在我看来是十分可贵的，我正需要这样一款 App。在我的 App 开发大部分完成后，我发现它和 iCost 仍有很多差距，iCost 某些方面的布局和设计十分巧妙，也正是我之后需要学习的地方。</p><p>虽然 App 的展示结束了，但我认为这款 App 的开发远没有结束。我的 commit 集中在 8 月 8 日的一周（86 commits）和 8 月 15 日的一周（10 commits），之后由于小学期、课程设计、训练营、选修课等无数 ddl 就只能搁置了，大约暂停了 25 天……直到最近才开始修改之前留下的各种 bug，还有很多功能需要完善……</p><h1 id="目前实现的功能"><a href="#目前实现的功能" class="headerlink" title="目前实现的功能"></a>目前实现的功能</h1><h2 id="主页"><a href="#主页" class="headerlink" title="主页"></a>主页</h2><ul><li>本月支出、本月收入、结余</li><li>一键隐藏</li><li>月预算、日预算</li><li>按日期筛选交易记录</li><li>按类型筛选每日交易记录</li></ul><h2 id="记一笔"><a href="#记一笔" class="headerlink" title="记一笔"></a>记一笔</h2><ul><li>选择交易类型（支出、收入、不计入收支）</li><li>添加备注</li><li>选择分类</li><li>选择日期和时间</li><li>填写金额</li></ul><h2 id="摇食堂"><a href="#摇食堂" class="headerlink" title="摇食堂"></a>摇食堂</h2><ul><li>随机选择</li><li>可编辑奖池</li></ul><h2 id="预算"><a href="#预算" class="headerlink" title="预算"></a>预算</h2><ul><li>预算卡片背景随剩余比例变换颜色</li><li>查看预算详情</li><li>预算期内交易明细</li><li>可编辑预算</li></ul><h2 id="搜索"><a href="#搜索" class="headerlink" title="搜索"></a>搜索</h2><ul><li>搜索按钮和界面</li></ul><h2 id="账本"><a href="#账本" class="headerlink" title="账本"></a>账本</h2><ul><li>选择账本</li><li>添加账本</li></ul><h2 id="统计"><a href="#统计" class="headerlink" title="统计"></a>统计</h2><ul><li>选择月份</li><li>显示当月总支出、总入账</li><li>显示当月的每日支出和收入对比</li><li>显示当年的每月支出和收入对比</li><li>长按折线图可显示具体金额</li><li>显示当月的支出排行榜（金额排序）</li><li>显示当月的全部支出排行</li><li>不同排序方式（按金额和按时间）</li></ul><h2 id="账户"><a href="#账户" class="headerlink" title="账户"></a>账户</h2><ul><li>添加不同类型账户</li><li>添加每个账户的资产和备注</li><li>自动计算总资产</li><li>添加负债（可选）</li><li>自动计算净资产</li></ul><h2 id="设置"><a href="#设置" class="headerlink" title="设置"></a>设置</h2><ul><li>记账日报</li><li>记账月报</li><li>分类管理</li><li>常见问题</li><li>问题反馈</li></ul><h1 id="TODO"><a href="#TODO" class="headerlink" title="TODO"></a>TODO</h1><p>还有很长的路要走……所以先告一段落吧，希望有朝一日可以把 App 完善得更好。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;可能是第一个比较成熟的 app。&lt;/p&gt;</summary>
    
    
    
    <category term="技术" scheme="https://superpung.com/tech/"/>
    
    
    <category term="Apple" scheme="https://superpung.com/tags/Apple/"/>
    
    <category term="iOS" scheme="https://superpung.com/tags/iOS/"/>
    
    <category term="SwiftUI" scheme="https://superpung.com/tags/SwiftUI/"/>
    
    <category term="Swift" scheme="https://superpung.com/tags/Swift/"/>
    
    <category term="App" scheme="https://superpung.com/tags/App/"/>
    
  </entry>
  
  <entry>
    <title>在 SwiftUI 中自定义修饰器</title>
    <link href="https://superpung.com/custom-modifier/"/>
    <id>https://superpung.com/custom-modifier/</id>
    <published>2021-07-13T02:41:14.000Z</published>
    <updated>2021-07-13T02:41:14.000Z</updated>
    
    <content type="html"><![CDATA[<p>学习 swiftUI 的记录。</p><span id="more"></span><p>首先定义结构体，在其中对内容修饰：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Watermark</span>: <span class="title class_ inherited__">ViewModifier</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> text: <span class="type">String</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">func</span> <span class="title function_">body</span>(<span class="params">content</span>: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> &#123;</span><br><span class="line">        <span class="type">ZStack</span>(alignment: .bottomTrailing) &#123;</span><br><span class="line">            content</span><br><span class="line">            <span class="type">Text</span>(text)</span><br><span class="line">                .font(.caption)</span><br><span class="line">                .foregroundColor(.white)</span><br><span class="line">                .padding(<span class="number">5</span>)</span><br><span class="line">                .background(<span class="type">Color</span>.black)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后根据此结构体，定义 <code>extension</code>，在其中定义修饰器函数：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extension</span> <span class="title class_">View</span> &#123;</span><br><span class="line">    <span class="keyword">func</span> <span class="title function_">watermarked</span>(<span class="params">with</span> <span class="params">text</span>: <span class="type">String</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> &#123;</span><br><span class="line">        <span class="keyword">self</span>.modifier(<span class="type">Watermark</span>(text: text))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最终只需调用 <code>extension</code> 中的函数即可实现：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Color</span>.green</span><br><span class="line">    .frame(width: <span class="number">300</span>, height: <span class="number">200</span>)</span><br><span class="line">    .watermarked(with: <span class="string">&quot;Hacking with Swift&quot;</span>)</span><br></pre></td></tr></table></figure><p>效果图：</p><img data-src="https://i0.hdslb.com/bfs/album/e411afa5889eabb69f3512b4f2a6771cfbb5bd97.png" alt="image-20210713104903030" style="zoom:50%;" />]]></content>
    
    
    <summary type="html">&lt;p&gt;学习 swiftUI 的记录。&lt;/p&gt;</summary>
    
    
    
    <category term="技术" scheme="https://superpung.com/tech/"/>
    
    
    <category term="SwiftUI" scheme="https://superpung.com/tags/SwiftUI/"/>
    
  </entry>
  
  <entry>
    <title>关于最近，以及……</title>
    <link href="https://superpung.com/rev-1/"/>
    <id>https://superpung.com/rev-1/</id>
    <published>2021-07-07T04:00:37.000Z</published>
    <updated>2021-07-07T04:00:37.000Z</updated>
    
    <content type="html"><![CDATA[<p>小记。</p><span id="more"></span><h1 id="关于大学生活已经过去了一半这件事"><a href="#关于大学生活已经过去了一半这件事" class="headerlink" title="关于大学生活已经过去了一半这件事"></a>关于大学生活已经过去了一半这件事</h1><p>大学生活度过了一半，仍旧在学期内忙碌、假期内无所事事两个状态间切换，却没有留下些什么。</p><p>看到许多同龄人在大学两年快速成长，收获颇丰，学习生活两开花，令人羡慕。</p><p>假期前度过的考试周着实令人感到紧张刺激，感觉是一个学期记忆里的高峰，也是学习新知识的最多阶段。</p><p>关于课业，鄙人的大类课程终于告一段落了，接下来将面对专业方向的选择，于是在这个历史性的阶段，想着整理一番两年的学习资料，与它们告别，同时便于后人翻阅。</p><p>考试周前在 GitHub 上偶然间看到了许多课程资料分享的 repo，于是 Google 了一下，发现本校尚无，遂自创之。</p><p>后在考试期间、放假之初也在想如何归纳，后来慢慢变得细化、有条理，最终花了几天时间创建完成。</p><p>本想着能节约一下本就贫瘠的硬盘空间，没想到百余次 commit 让 .git 文件夹达到了 10 G，是资料文件的好几倍……</p><p>算了，10 G 就 10 G 吧……</p><p>就放在 <a href="https://github.com/SuperPung/TJU-CourseSharing">这里</a> 了，欢迎有缘人看到提提 Issue 或 PR……</p><p>希望这个项目可以帮助到更多的人吧……</p><h1 id="关于-2021-年过去了一半却还没有总结-2020-年这件事"><a href="#关于-2021-年过去了一半却还没有总结-2020-年这件事" class="headerlink" title="关于 2021 年过去了一半却还没有总结 2020 年这件事"></a>关于 2021 年过去了一半却还没有总结 2020 年这件事</h1><p>2020 年感觉经历了很多，本想在寒假的时候认真总结一下，但却又想不出什么值得纪念的事情。</p><blockquote><p>从人类尺度上来说，时间是连续的，所谓的年也不过是人为根据天体运行规律与气候所做的尺度划分。</p><p>虽然从回忆中单单拎出任何一件事来说，都不足以道，但是聚在一起，反倒如潮水般，暗流奔涌。</p></blockquote><p>无数件小事堆叠、重合，不断推搡着时间前进，有时多得让人喘不过气，有时空闲得静看庭花。</p><p>对所有人来说非同寻常的 2020 年，不知改变了多少人的人生轨迹。</p><p>疫情束缚了人的脚步，史无前例的线上教学，给了我莫大的自由空间。</p><p>2020 年，继续在博客上活跃，也尝试了公众号（因为发了很多实验讲解导致同学关注的很多），找到了喜欢做的事。</p><p>2020 年， 举行了线上发布会，这也是我开始凌晨一点守候  发布会的开端。从 WWDC20 开始， 至今所有的发布会都熬夜看完直播。从惊艳的 iOS 14、iPadOS 14，还有 macOS Big Sur；从 iPad Air、Apple Watch 6 到 iPhone 12、Apple Silicon M1、新的 MacBook Air、MacBook Pro、Mac mini，再到不痛不痒的 iOS 15、iPadOS 15、macOS Monterey……一年多的时光， 正潜移默化地影响着我的生活。</p><p>第一次接触 macOS 是官网的 Mojave 介绍，壁纸随着时间的推移，光影也在变化。后来了解到了黑苹果，最后熬了几个夜终于给装好了，用了一年多没出现异常。</p><p>后来换了真正的 MacBook，原来的电脑也结束了使命，重装回了 Windows。</p><p>在此记录一下，以纪念陪伴我一年的 Hackintosh。</p><p>2020 年，被困在内，但也可能是我向外迈出的第一步。参加了创新计划，最终顺利结题。参加了暑期训练营，第一次写应用，还获得了小奖（那段时间忙得甚至没来得及记录……）。第一次参加社团，当上了部长，组织了几次活动，有些成就感。</p><p>鄙人虽然才疏学浅，但很喜欢向别人分享学到的知识。2020 年报名了新生小班，遇到了非常厉害的几位“同事”，也接触了许多刚入大学的新生。</p><p>有幸指挥了开学典礼，印象深刻的是前夜的准备。</p><p><img data-src="https://i0.hdslb.com/bfs/album/983fa39e232ddf4e768cd8fd531cc306b1d0d2c3.png" alt="before-open"></p><p>（这张图片拍的不错，出自摄影师之手）</p><p>准备完，回宿舍的路上，安静的没有人，忽然觉得很自由。</p><p><img data-src="https://i0.hdslb.com/bfs/album/e504754383446308a1ad316789d060888fa3fdaa.jpg" alt="back-to-dom"></p><p>（这张图片当然是我自己拍的，但不是趴在地上）</p><p>新生周结束的时候，好多小班都感慨颇深。</p><blockquote><p>工作很辛苦，几乎把宿舍搬到了办公室，随时回消息，提前商量通知，虽然累，但是很快乐，一起吐槽奇葩问题，一起欣慰工作顺利，脑洞大开，脑回路拓宽，一起唱歌，一起蹦迪，一起吃喝，一起复习准备考试，一起湖边看星星唱歌。</p><p>好久没有这种一个团队一起付出的感觉了，这三周我很开心，很回味，机会不再有，友谊不会散~</p><p>——hrq</p></blockquote><blockquote><p>太多的话来不及表达，太多的人来不及道别，太多的事来不及结尾。</p><p>时过境迁，岂敢奢求驻足暂歇</p><p>幸甚至哉，得同道之人渡日夜</p><p>愿君前程皆坦途，往来莫徘徊</p><p>——hzy</p></blockquote><blockquote><p>永远不要甘于自己的平庸，永远对梦想和目标满怀热忱，永远永远不要习惯懒惰。</p><p>——zyn</p></blockquote><blockquote><p>面对大学里的众多选择，我曾选择逃避，选择承认自己的平庸然后自暴自弃。这段操心而忙碌的时光就像闹铃一般吵醒了我，让我突然摆脱了之前浑浑噩噩的状态，找到了自己为之奋斗的目标。</p><p>请永远保持踏进校门时心中的那份希冀与热忱。</p><p>——zyh</p></blockquote><p>还有很多，现在可能也回忆不起来了。只记得那是一个夏夜，十几个少年在湖边且行且歌。</p><p><img data-src="https://i0.hdslb.com/bfs/album/e24b44edbed70c0063f954979c9177de7723a0f6.jpg" alt="xiaobans-and-stars"></p><p>有星空相伴，亦如星般璀璨。</p><p>2020 年，有些事情确实没有白做，但得到的同时也伴有失去。没有什么两全其美的办法，尽力去弥补就好了。</p><p>在此记录一下，以纪念逝去的 2020。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;小记。&lt;/p&gt;</summary>
    
    
    
    <category term="生活" scheme="https://superpung.com/life/"/>
    
    
  </entry>
  
  <entry>
    <title>《数据库原理》笔记</title>
    <link href="https://superpung.com/fcds-notes/"/>
    <id>https://superpung.com/fcds-notes/</id>
    <published>2021-06-28T12:56:49.000Z</published>
    <updated>2021-06-28T12:56:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>《数据库系统基础教程》《数据库系统全书》</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;《数据库系统基础教程》《数据库系统全书》&lt;/p&gt;
</summary>
      
    
    
    
    <category term="笔记" scheme="https://superpung.com/note/"/>
    
    
    <category term="数据库" scheme="https://superpung.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>《数字逻辑与数字系统》笔记</title>
    <link href="https://superpung.com/ddca-notes/"/>
    <id>https://superpung.com/ddca-notes/</id>
    <published>2021-06-24T07:10:29.000Z</published>
    <updated>2021-06-24T07:10:29.000Z</updated>
    
    <content type="html"><![CDATA[<p>《数字设计和计算机体系结构》</p><span id="more"></span>]]></content>
    
    
    <summary type="html">&lt;p&gt;《数字设计和计算机体系结构》&lt;/p&gt;</summary>
    
    
    
    <category term="笔记" scheme="https://superpung.com/note/"/>
    
    
    <category term="Computer System" scheme="https://superpung.com/tags/Computer-System/"/>
    
  </entry>
  
  <entry>
    <title>《形式化方法》笔记</title>
    <link href="https://superpung.com/fsv-notes/"/>
    <id>https://superpung.com/fsv-notes/</id>
    <published>2021-06-22T12:50:21.000Z</published>
    <updated>2021-06-22T12:50:21.000Z</updated>
    
    <content type="html"><![CDATA[<p>Formal Specification Using Z by David Lightfoot</p><span id="more"></span>]]></content>
    
    
    <summary type="html">&lt;p&gt;Formal Specification Using Z by David Lightfoot&lt;/p&gt;</summary>
    
    
    
    <category term="笔记" scheme="https://superpung.com/note/"/>
    
    
    <category term="形式化方法" scheme="https://superpung.com/tags/%E5%BD%A2%E5%BC%8F%E5%8C%96%E6%96%B9%E6%B3%95/"/>
    
  </entry>
  
</feed>
