<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Hao_Tian</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://haotian22.top/</id>
  <link href="https://haotian22.top/" rel="alternate"/>
  <link href="https://haotian22.top/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Hao_Tian</rights>
  <subtitle>Hao_Tian的博客(｡･∀･)ﾉﾞ嗨</subtitle>
  <title>Hao_Tian的折腾日志</title>
  <updated>2026-07-04T02:00:00.000Z</updated>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <category term="AI" scheme="https://haotian22.top/tags/AI/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在使用 Hermes Agent时，发现它虽然功能强大，但默认无法直接访问 Outlook、Gmail、Notion 这类外部服务</p><p>每次想让 AI 帮我查个日程或者读封邮件，都得手动操作后再告诉它，或者手动一个个配置 MCP 或者 CLI，显得非常繁琐</p><p>后来发现了 <a href="https://composio.dev/">Composio</a> 这个平台——它通过 MCP（Model Context Protocol）协议，为 AI Agent 提供了 500+ 外部服务的统一接入能力。接上之后，AI 助手就能直接操作我的 Outlook 日历、读写邮件，甚至管理 Notion 页面</p><p>这篇文章记录了整个接入过程，包括遇到的坑和解决方案</p><h2 id="什么是-Composio-和-MCP"><a href="#什么是-Composio-和-MCP" class="headerlink" title="什么是 Composio 和 MCP"></a>什么是 Composio 和 MCP</h2><h3 id="MCP（Model-Context-Protocol）"><a href="#MCP（Model-Context-Protocol）" class="headerlink" title="MCP（Model Context Protocol）"></a>MCP（Model Context Protocol）</h3><p>MCP 是 Anthropic 提出的一个开放协议，用于标准化 AI 模型与外部工具&#x2F;数据源之间的通信。你可以把它理解为 AI 世界的”USB 接口”——任何支持 MCP 的 AI 应用，都可以通过统一的方式连接到各种外部服务</p><h3 id="Composio"><a href="#Composio" class="headerlink" title="Composio"></a>Composio</h3><p>Composio 是一个 MCP 服务提供商，它把 500+ 外部应用（Gmail、Outlook、Slack、GitHub、Notion、Salesforce 等）封装成了 MCP 工具。对于 AI Agent 来说，接入 Composio 就等于一次性获得了连接数百种服务的能力。</p><table><thead><tr><th>类别</th><th>数量</th><th>典型应用</th></tr></thead><tbody><tr><td>开发工具</td><td>124</td><td>GitHub, GitLab, Supabase, Linear, Jira, Vercel</td></tr><tr><td>效率办公</td><td>147</td><td>Notion, Asana, Airtable, Google Tasks, Todoist</td></tr><tr><td>CRM</td><td>46</td><td>HubSpot, Salesforce, Pipedrive, Zoho CRM</td></tr><tr><td>沟通协作</td><td>55</td><td>Slack, Discord, Microsoft Teams, Gmail, Outlook</td></tr><tr><td>文档管理</td><td>76</td><td>Google Drive, Google Docs, Google Sheets, OneDrive</td></tr><tr><td>金融财务</td><td>52</td><td>Stripe, QuickBooks, Xero, Plaid</td></tr><tr><td>营销社交</td><td>98</td><td>Twitter&#x2F;X, LinkedIn, Mailchimp, Meta Ads</td></tr><tr><td>电商</td><td>34</td><td>Shopify, WooCommerce, Amazon Seller</td></tr><tr><td>AI&#x2F;ML</td><td>79</td><td>OpenAI, Anthropic, Hugging Face, Pinecone</td></tr></tbody></table><h2 id="配置过程"><a href="#配置过程" class="headerlink" title="配置过程"></a>配置过程</h2><h3 id="第一步：添加-Composio-MCP-服务器"><a href="#第一步：添加-Composio-MCP-服务器" class="headerlink" title="第一步：添加 Composio MCP 服务器"></a>第一步：添加 Composio MCP 服务器</h3><p>因为我使用的是 Windows 系统，并没有 Composio 的 CLI 适配，只能使用 MCP 来连接了</p><p>虽然 <a href="https://composio.dev/hermes">composio.dev&#x2F;hermes</a> 上写可以通过 Hermes 的 Agent 模式来自动配置，但是其实还是有很多问题的，一直报错返回多花了我近 100k 的 token</p><p>所以最好的方法还是手动先配置一下然后再用 Agent 检查</p><p>所以，应该先在命令行执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes mcp add composio --url https://connect.composio.dev/mcp --auth oauth</span><br></pre></td></tr></table></figure><p>理论上执行完后，打开 <code>config.yaml</code>，可以看见以下的内容</p><figure class="highlight yaml"><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="attr">mcp_servers:</span></span><br><span class="line">  <span class="attr">blender:</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">uv</span></span><br><span class="line">    <span class="attr">args:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">--directory</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">D:\Program\blender_mcp\mcp</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">run</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">blender-mcp</span></span><br><span class="line">  <span class="attr">composio:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">https://connect.composio.dev/mcp</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">auth:</span> <span class="string">oauth</span></span><br></pre></td></tr></table></figure><p>（Blender 那部分可以不用管，只是表明一下 MCP 配置的结构）</p><p>如果发现不对的话大概需要再手动编辑一下</p><h3 id="第二步：验证连接"><a href="#第二步：验证连接" class="headerlink" title="第二步：验证连接"></a>第二步：验证连接</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes mcp <span class="built_in">test</span> composio</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></pre></td><td class="code"><pre><span class="line">Connection: ✓ (10.7s)</span><br><span class="line">Tools: 7/7 enabled</span><br></pre></td></tr></table></figure><h3 id="第三步：重启会话使工具生效"><a href="#第三步：重启会话使工具生效" class="headerlink" title="第三步：重启会话使工具生效"></a>第三步：重启会话使工具生效</h3><p>MCP 工具添加后，需要在新会话中才能使用。在 Hermes TUI 中执行 <code>/reset</code>，或者启动一个新的 <code>hermes chat</code> 会话。</p><p>重启后，应该能看到 7 个 Composio 工具：</p><table><thead><tr><th>工具名</th><th>用途</th></tr></thead><tbody><tr><td><code>COMPOSIO_SEARCH_TOOLS</code></td><td>搜索 500+ 应用集成</td></tr><tr><td><code>COMPOSIO_MANAGE_CONNECTIONS</code></td><td>管理应用连接（OAuth 授权）</td></tr><tr><td><code>COMPOSIO_MULTI_EXECUTE_TOOL</code></td><td>并行执行多个工具调用</td></tr><tr><td><code>COMPOSIO_GET_TOOL_SCHEMAS</code></td><td>获取工具的参数定义</td></tr><tr><td><code>COMPOSIO_REMOTE_BASH_TOOL</code></td><td>远程沙箱执行命令</td></tr><tr><td><code>COMPOSIO_REMOTE_WORKBENCH</code></td><td>批量工具执行和远程文件处理</td></tr><tr><td><code>COMPOSIO_WAIT_FOR_CONNECTIONS</code></td><td>等待 OAuth 授权完成</td></tr></tbody></table><h3 id="第四步：连接你的应用"><a href="#第四步：连接你的应用" class="headerlink" title="第四步：连接你的应用"></a>第四步：连接你的应用</h3><p>Composio 接入后，登录获取 OAuth 授权</p><p>在 Hermes 对话中直接告诉 AI：</p><blockquote><p>“帮我使用 Composio 读取我最新的 Outlook 邮件”</p></blockquote><p>这时候，如果没有登录成功的话，Agent 应该会自动打开一个浏览器，登录你的 Composio 账号，点击连接授权，就可以正常使用了</p><p>另外，其实我还在 hermes 的 <code>.env</code> 的最后面加了一行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MCP_COMPOSIO_API_KEY=ck_xxxxxxxxxxxxxxx</span><br></pre></td></tr></table></figure><p>我不知道是不是需要加的，如果还是连不上的话可以试试加上这一行</p><h2 id="实际效果演示"><a href="#实际效果演示" class="headerlink" title="实际效果演示"></a>实际效果演示</h2><p>接入完成后，AI 助手可以直接操作你的外部服务。以下是几个实际使用场景：</p><h3 id="查看-Outlook-日程"><a href="#查看-Outlook-日程" class="headerlink" title="查看 Outlook 日程"></a>查看 Outlook 日程</h3><p>直接对 AI 说”查看我最近的日程”，AI 会：</p><ol><li>调用 <code>COMPOSIO_SEARCH_TOOLS</code> 搜索日历相关工具</li><li>使用 <code>OUTLOOK_GET_CALENDAR_VIEW</code> 获取日程数据</li><li>将结果整理成表格返回</li></ol><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><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">📅 即将到来的日程</span><br><span class="line"></span><br><span class="line">| 日期       | 时间       | 事项                | 地点               |</span><br><span class="line">|-----------|-----------|--------------------|--------------------|</span><br><span class="line">| 7月13日    | 10:00-15:00 | 模拟选课（试选）     | 选课系统        |</span><br><span class="line">| 7月20日    | 10:00-13:00 | 正式选课第一轮       | 选课系统        |</span><br><span class="line">| 7月20日    | 15:00-18:00 | 正式选课第二轮       | 选课系统        |</span><br></pre></td></tr></table></figure><h3 id="读取邮件"><a href="#读取邮件" class="headerlink" title="读取邮件"></a>读取邮件</h3><p>AI 可以调用 <code>OUTLOOK_FETCH_EMAILS</code> 或 <code>GMAIL_FETCH_EMAILS</code> 来读取最新邮件，包括发件人、主题、时间等信息。</p><h3 id="操作-Notion-GitHub-等"><a href="#操作-Notion-GitHub-等" class="headerlink" title="操作 Notion &#x2F; GitHub 等"></a>操作 Notion &#x2F; GitHub 等</h3><p>只要在 Composio 中完成了对应的 OAuth 授权，AI 就能操作 Notion 页面、创建 GitHub Issue、发送 Slack 消息等等。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>接入 Composio 之后，Hermes Agent 从一个”只能聊天”的 AI 变成了一个真正能帮你干活的助手——查日程、读邮件、管理待办、操作文档，全部通过对话完成。</p><p>整个接入过程的核心其实就是一条命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hermes mcp add composio --url https://connect.composio.dev/mcp --auth oauth</span><br></pre></td></tr></table></figure><p>剩下的就是逐个为需要的应用完成 OAuth 授权。如果你也在用 Hermes Agent，强烈推荐试试。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://composio.dev/">Composio 官网</a></li><li><a href="https://composio.dev/hermes">Composio + Hermes 集成页面</a></li><li><a href="https://docs.composio.dev/">Composio 文档</a></li><li><a href="https://modelcontextprotocol.io/">MCP 协议规范</a></li><li><a href="https://hermes-agent.nousresearch.com/docs">Hermes Agent 文档</a></li><li><a href="https://discord.gg/composio">Composio Discord</a></li></ul>]]>
    </content>
    <id>https://haotian22.top/963e7520.html</id>
    <link href="https://haotian22.top/963e7520.html"/>
    <published>2026-07-04T02:00:00.000Z</published>
    <summary>通过 Composio MCP Server 为 Hermes Agent 接入 500+ 外部服务（Outlook、Gmail、Notion、Slack等），实现 AI 助手直接操作你的日历、邮件和工作流。</summary>
    <title>在Hermes Agent中接入Composio实现500+外部服务集成</title>
    <updated>2026-07-04T02:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在折腾 Zashboard 时，遇到了一个非常诡异的问题：</p><p>明明应该下载的是 .json 配置文件，它的导入也是要求的 .json 文件，结果浏览器下载出来的文件却全部变成了：</p><p>zashboard-settings.customization</p><p>一开始我以为是 zashboard 自己定义了奇怪的文件格式，故意使用了 .customization 后缀，来表明它们是两个不同的文件，但是询问后发现它们逻辑上就是一个文件</p><p>但事情并没有那么简单</p><h2 id="经过"><a href="#经过" class="headerlink" title="经过"></a>经过</h2><p>我尝试了很多方法来解决这个问题：更换浏览器、清除缓存、Incognito 模式、更换 Zashboard 版本、但问题依旧存在</p><p>后面，我注意到了 .customization 的默认打开方式是 Windows Journal App，便尝试卸载了 Journal App，结果问题解决了，下载的文件后缀又变回了 .json</p><p>我后面询问了 AI，经过自己的检查，得到了以下的答案：（所以以下内容都是 AI 写的）</p><p>打开注册表，进入 <code>HKEY_CLASSES_ROOT\MIME\Database\Content Type\application/json</code></p><p>发现一个非常离谱的东西：</p><p><code>Extension = .customization</code></p><p>而正常情况下应该是：</p><p><code>Extension = .json</code></p><p>也就是说：</p><p>这个 Journal App 把全局的：</p><p>application&#x2F;json</p><p>MIME 类型关联到了：</p><p>.customization</p><p>于是 Windows 告诉浏览器：</p><p>“所有 JSON 下载，都应该保存成 .customization。”</p><p>浏览器就乖乖照做了。</p><p>所以下载逻辑变成：</p><p>服务器返回：<br>Content-Type: application&#x2F;json</p><p>↓</p><p>Windows 查询 MIME</p><p>↓</p><p>application&#x2F;json → .customization</p><p>↓</p><p>浏览器保存：<br>xxx.customization</p><p>破案</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>Journal App 修改了 Windows 全局 MIME 注册表，把 application&#x2F;json 的默认扩展名改成了 .customization，导致所有下载的 JSON 文件都被错误地命名为 .customization</p><p>修复这个问题，把 <code>HKEY_CLASSES_ROOT\MIME\Database\Content Type\application/json</code> 里面的 <code>Extension</code> 键值改成 <code>.json</code> ，就能解决了</p>]]>
    </content>
    <id>https://haotian22.top/7fe188cf.html</id>
    <link href="https://haotian22.top/7fe188cf.html"/>
    <published>2026-06-07T07:10:28.000Z</published>
    <summary>一次由 Journal App 污染 Windows MIME 注册表导致 JSON 下载后缀异常的排查经历</summary>
    <title>Windows Journal自动把下载的.json改成.customization的奇怪Bug</title>
    <updated>2026-06-07T07:10:28.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="随笔" scheme="https://haotian22.top/tags/%E9%9A%8F%E7%AC%94/"/>
    <category term="思考" scheme="https://haotian22.top/tags/%E6%80%9D%E8%80%83/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>这其实又是一篇学校作业的产物，原本的要求是“请为一个或多个动漫、游戏、电影（等等）撰写一篇评论”，因为可以允许直接提交中文，所以我才敢选了这样一个我不熟悉而且几乎没接触过的切入选题</p><p>但是，我终究也不是读哲学的，本来是在5月20日就决定选题开始写了（因为情人节突发奇想就想做点爱情相关的主题，另外我自己也一直搞不懂相关的问题，还挺有兴趣的），一直因为资料和理论不熟悉难产到了现在，还好AI帮忙找资料，文章可能有偏见，可能有错误，请原谅</p><p>说到AI，这篇文章全部都是我手打的，我原本试图用AI给我写一点草稿，但是发现它的内在论证逻辑很乱，试了很多次都和我原本的论点有点偏差，最终就只是把它说的部分有道理的论点挑出来自己组织一遍语言融合进我的文章里面</p><h2 id="《终将成为你》中爱情凝视的枷锁"><a href="#《终将成为你》中爱情凝视的枷锁" class="headerlink" title="《终将成为你》中爱情凝视的枷锁"></a>《终将成为你》中爱情凝视的枷锁</h2><p>在恋爱相关主题的动画中，“喜欢”可能是贯穿12集的拉扯，可能是结尾最终的一句告白……而在这部《终将成为你》里，别人的喜爱，对七海灯子来说，反而是一把枷锁。七海灯子最初喜欢上小糸侑，也正是因为侑“不会爱上她”的特性。作者通过灯子这看似矛盾的逻辑，对“爱”与“被爱”的权力结构作出了更深的探讨：为了继续得到别人的喜欢，就要放弃原有的自由，抬高自己的标准，迎合对方的期待。即使动画的末尾，侑提出了“无条件”的爱，矛盾似乎也未能真正解决——这种“无条件的爱”能持续吗？本文希望从萨特、弗洛姆等理论入手，分析本作爱情中的隐性压力，以及爱情究竟意味着什么。</p><h3 id="爱情的压力"><a href="#爱情的压力" class="headerlink" title="爱情的压力"></a>爱情的压力</h3><p>要深入探究这种复杂的关系和逻辑，还需要从距离这个问题本身非常遥远的位置，灯子形象的起源，开始入手。动画第六集后半部分，在侑的追问下，灯子坦白了她内心的秘密：“希望自己能够成为姐姐的替代品”——她的姐姐以优秀学生的形象活在了很多人的印象中，而姐姐因车祸的离去，周围人便把优秀的希望寄托到了妹妹灯子的身上。为了迎合他人的期待，灯子选择在除了侑的其他人面前带上了优秀的学生会长的面具。然而，就如萨特的存在主义所描述的，灯子一直努力地扮演完美会长和姐姐的替代品这两个角色，而不承认自己是一个可以随时改变的自由的人，本质是一种“Bad Faith” (Rahman &amp; Mehnaz, 2024)。他认为，这种自欺是对面对真实、自由的自我的逃避；这也是剧中灯子对“被喜欢”的恐惧的重要来源之一：虽然她也知道自己是在表演，但是一旦放弃了表演，就不知道真实的自己是什么，更恐惧真实的自己不值得“被爱”，这是灯子不愿意承受的。</p><p>萨特提出“他者(the Other)”的概念进一步指出：当人被他人关注之时，“我”就不再只是主体，而是变成了他人的客体；他人的目光代替“我”给自己下了定义。而在众多的社会关系中，爱情作为最亲密的关系，这种“注视”也是最强烈的。这也是故事中灯子恐惧爱情的原因之二：别人越是喜欢自己，自己的自由和主观性就越少，越活在他人的印象中。至此，灯子对“被喜欢”的恐惧就有了清晰的来源：一方面，她借助了他人的期待来扮演好优等生的角色，逃避了对自己本质的展现；另一方面，她又知道这种凝视会将她客体化，失去改变的自由。但是，灯子也并不是单纯的这套爱情压力的受害者，她也是自私的：她不愿意接受“被注视”，但是自己却利用了这套因喜欢而产生的注视和期待的逻辑限制着侑，让她“不要喜欢上我”，根据侑的初始性格把侑的未来定义在了无法定义她的位置上。</p><h3 id="枷锁的松绑"><a href="#枷锁的松绑" class="headerlink" title="枷锁的松绑"></a>枷锁的松绑</h3><p>在动画的7-13集，侑意识到了灯子的困境后，站在了一个更为主动的位置上，尝试对灯子这种矛盾的状态使用自己的力量做出改变：主动接触剧团的同学，了解灯子周围的人际关系；深入了解她的童年背景与和姐姐的关系，更多地给予灯子摘下面具的自由。侑这种行动的方式，和弗洛姆《爱的艺术》中的实践理论相吻合。弗洛姆把爱拆分成为了四个基本要素：关心、责任心、尊重和了解 (Fromm, 2008)。这四个要素之中，他把了解看作其中的基础：如果不真正了解对方，关心和责任心都会是盲目的，尊重也无从谈起。这一阶段侑开始做的，就是重新认识灯子，她不满足于灯子呈现给其他人那样的完美会长形象，执意走向灯子那连她自己都不愿意面对的内心。</p><p>在弗洛姆的进一步论述中提到，爱更多的是给予——给予他人自己的创造力和活力。侑对灯子的爱，起源和动力给予本身，她可以通过帮助灯子走出困境来获得快乐，而不是获得她的回报，这也恰好避开了灯子所害怕的期待的压力。侑这种持续的给予也无意识地构成了心理学家罗杰斯定义的“无条件积极关注”的核心 (Rogers, 2004)。罗杰斯认为，这种不受个体行为影响的无条件的关怀、接纳和重视的态度可以给予个体自主性和安全感，促进个人成长和自我实现。侑对灯子的关注没有将灯子锁定在任何一个臆想中的形象里，没有附加任何条件。在结尾的两集，那个让灯子饰演“失忆少女”的学生会校园剧里，侑修改了校园剧的剧本，选择了让灯子饰演的“失忆少女”选择当下的自己，不再需要在过去在不同人面前的不同形象之间选择，接纳了这种不确定的中间的状态。这使得灯子可以进一步依靠侑而不用担心关系破裂，进而逐渐认识到自己的价值，完成自爱的成长。</p><h3 id="动画的局限"><a href="#动画的局限" class="headerlink" title="动画的局限"></a>动画的局限</h3><p>但，动画的爱情终究还是理想和美化过的剧场，并没有给现实中处于更复杂的爱情环境中我们找到真正解决问题的答案：动画中通过尊重和接纳自由地发展，解开了这层名为“爱”的枷锁，看似是自由了，但是也在无意中提出了一个更深层的疑问：固定形象被消解之后，长期关系的维系又该依靠什么？爱的本质究竟是什么？</p><p>如果说，爱的动力，是弗洛姆说的“给予”的快乐，那么在现实中，侑展现的这种动力，会随着时间削弱吗？心理学家斯滕伯格提出的“爱情三角论”认为，爱情由亲密、激情和承诺三个要素构成 (Sternberg, 1986)。其中能够使得爱情持续化的，主要在于做出的承诺。故事中，侑对于灯子做出的承诺是非常具体的，几乎完全建立在“帮助灯子成长”之上，但当未来这个情景消失了，如何获取其他的承诺，或者如何将这种情感转化为承诺，动画并没有给出答案。</p><p>另一个值得追问的问题是，即使这种“给予”的动力源源不断，为什么这个爱的对象偏偏是她，而不是任何其他人？如果说故事中是那个“迷茫的灯子”和“需要被帮助的灯子”形象产生的责任感和使命感，那么当灯子的形象随成长而改变之时，想要“给予”的首选对象会发生转移吗？这触及到了“忒修斯之船”的哲学问题，当人物形象一点点逐步改变之后，爱的“对象”还是原来那个人吗？这和其他任何人又有什么不同？</p><p>这或许就是《终将成为你》这部动画作为青春动画形式的局限所在。校园青春爱情动画的叙事，不同于现实纪录片，服务于呈现故事困境与破局，而不是展现一个长达数年的事件记录。其中的探讨，给了观众一定的思考切入点，但是后续的问题与实践，始终都还需要屏幕外的观众独自思考和面对。</p><h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>总而言之，《终将成为你》从爱与被爱的矛盾开始，用细腻的感情描写，颠覆了传统恋爱故事中浪漫化的发展思路，创新性地讨论了爱情之中的关系和困境。本文利用萨特、弗洛姆和罗杰斯等理论，解析了压力的来源，破局的方法，尝试去用不同的方法来解释这个问题，但是也不得不承认，这些论述似乎并没有给现实的爱情得出一个让人满意的终极答案。</p><p>或许，就如同动画最后，大家都接受了灯子形象不确定性和未来发展的自由一样，在现实中的爱情，也并不是一个拥有已有确定答案的问题，只能接受它的不确定性，知晓这些对爱情研究的理论，再倾听自己内心的真实意向，自由地探索，得出自己独特的答案。</p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><p>Fromm, E. (2008). <em>爱的艺术</em> [The art of loving] (Li, J., Trans.). 上海译文出版社. (Original work published 1956)</p><p>Rahman, P., &amp; Mehnaz, S. (2024). International Journal for Multidisciplinary Research (IJFMR). <em>SSRN Electronic Journal</em>. <a href="https://doi.org/10.2139/ssrn.5054029">https://doi.org/10.2139/ssrn.5054029</a></p><p>Rogers, C. R. (2004). <em>成为一个人：一个治疗者对心理治疗的观点</em>（杨广学、尤娜、潘福勤，译）. 中国人民大学出版社. （原著出版于1961年）</p><p>Sternberg, R. J. (1986). A triangular theory of love. <em>Psychological Review</em>, <em>93</em>(2), 119–135. <a href="https://doi.org/10.1037/0033-295X.93.2.119">https://doi.org/10.1037/0033-295X.93.2.119</a></p>]]>
    </content>
    <id>https://haotian22.top/5d7f1f50.html</id>
    <link href="https://haotian22.top/5d7f1f50.html"/>
    <published>2026-05-31T17:41:12.000Z</published>
    <summary>这篇文章是我对动画《终将成为你》中爱情关系的分析，主要探讨了爱情中的压力和枷锁，以及如何通过理解和接纳来解开这些枷锁。文章结合了萨特、弗洛姆和罗杰斯等理论，试图从不同角度解释爱情中的矛盾和困境</summary>
    <title>《终将成为你》中爱情凝视的枷锁</title>
    <updated>2026-05-31T17:41:12.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="游戏" scheme="https://haotian22.top/tags/%E6%B8%B8%E6%88%8F/"/>
    <category term="思考" scheme="https://haotian22.top/tags/%E6%80%9D%E8%80%83/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>（本来是某个课程的一个作业，这个话题写得挺有意思的，就放在这里了，长文警告）</p><p>MiSide，这一个由两位俄罗斯人的小工作室制作出来的恐怖游戏，在2024年底发售之时，就引起了游戏社区的大量关注，因为它二次元女友的表皮和恐怖内核的反差，与轻微的Meta元素，在社区讨论中也经常与《心跳文学部》《君与彼女与彼女之恋》并列。</p><p>游戏本身的剧情，在网上已经有不少解读了。但，在游戏之外，社区的讨论，却是比较少有系统性讲解的。这款游戏，在发售后的Bilibili社区和Discord讨论区中（主要是中文群体），出现了不少对游戏剧情内容进行逐帧分析的解读视频，他们都自成体系而且都能自圆其说，而且甚至出现了不少看起来是“阴谋论”的解读，比如“善良米塔是疯狂米塔假扮的”，“黄毛NPC其实在秘密反抗”等，这是在其他游戏作品中较为少见的。所以，为什么，这个游戏，会催生如此大规模自发的对剧情的“理论”呢？本文将聚焦于游戏的媒介特性，碎片化的叙事，网络媒体的参与式文化几个角度来解释这件事。</p><p>游戏的形式，不同于电影和小说，天生就是碎片化的。Henry Jenkins在它的文章《Game Design as Narrative Architecture》中提出了“embedded narratives”和“environmental storytelling”的概念，表明游戏的叙事比起其他形式的被动接受，它更多需要通过玩家在游戏场景中探索和解读，把碎片化的信息拼凑起来，得出自己对故事的理解。而MiSide这款游戏，在场景叙事，也就是“彩蛋”方面，表现得非常出色：主角书架上的摩斯电码，对不同游戏和电影致敬的书本，关于AI的海报……这些小细节吸引了大量人一个个物件来解读。而且因为游戏包含了Meta的要素，玩家社区无法分辨他们到底是游戏原本剧情的一部分，还是真的只是制作者为了搞笑整活直接和玩家对话的彩蛋，例如，米拉藏在桌子底下的时候不开作弊其实表情是非常难以看见的，但是游戏里面又给她的这部分做了表情和动作；一个书柜顶玩家一般无法看见的角落，放着一本封面为“如何杀死所有人”的书……这种普通视角难以看到的东西，在游戏里面其实还有很多。因此，这些及其繁多，单个信息量小，又关联性不强的碎片，使得不同玩家在自由探索的过程中会看见不同的物件，玩家又再选取不同的物件产生联系，就很自然地在脑海里面构建出来不同的叙事。此外，类似于Roland Barthes的“作者已死”的理论，3D游戏，作者本身都甚至无法准确预测玩家观察物体的角度，使得产生出来的“文本”本身也是有一定不确定性的，更加促进了社区多元化的解读。</p><p>其实，这种现象，也并不是在近期才发生的，相关的理论其实可以追溯到20世纪。“文本盗猎”这一个此就是在1992年Henry Jenkins 研究电影粉丝文化的时候提出来的，具体是指粉丝“成为文本意义的构建和传播的积极参与者”。而如今，互联网和Bilibili的“二创”文化的发展也使得这些分析性和创意性的观点传播地更广和有利于更多的人在粉丝的“解读理论”之上再增添自己的解读，形成了今天这样的讨论局面。</p><p>总的来说，这种社区现象的产生，主要来源于游戏本身的特性，辅以粉丝文化的壮大。在和其他同学的交流中，发现这种现象在《黑暗之魂》等游戏社区中其实也会经常出现。而在我看来，这个每个人都能在网上产生影响力的时代，这种社区文化，也一定会越来越多。</p>]]>
    </content>
    <id>https://haotian22.top/13c65236.html</id>
    <link href="https://haotian22.top/13c65236.html"/>
    <published>2026-05-22T17:09:49.000Z</published>
    <summary>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla]]>
    </summary>
    <title>未完成的拼图——MiSide玩家社区的文本盗猎</title>
    <updated>2026-05-22T17:09:49.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这是一个奇怪的需求，起因是因为在学校的公用 MacStudio 里面给视频配音，发现有不少资源下载都没法直接访问</p><p>虽然理论上是可以用我的 iPad 下载完再 Airdrop 过来，但总觉得麻烦，想直接在 Mac 上下载</p><p>然而，MacOS 不同于 Windows，它并没有 Portable Software 的概念，几乎所有的软件都会将应用数据生成到 用户目录下的 Library 里面</p><p>如果是自己电脑的话，这其实也没什么大不了的，但是对于公用电脑，每次我使用都需要手动导入我需要的配置文件，也为了避免其他人滥用，每次离开的时候都要手动删除这些文件，还是挺麻烦的</p><p>另外，更要命的一点是，本身更改MacOS的网络代理设置就需要管理员权限，这就导致了我无法直接在普通用户权限下使用网络代理</p><h2 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h2><p>所以，我最终选择了一个非常麻烦但可行的方案：直接跑代理软件核心，搭配 Dashboard 来控制，并使用 Firefox 来切换代理</p><p>这个方案其实非常巧妙，Firefox 是为数不多的，可以直接指定配置用户文件夹的浏览器，也是为数不多可以额外指定代理设置的浏览器</p><p>所以，我使用了官方的 DMG 安装包，拖到了 U 盘里，然后执行以下命令：（如果有权限问题，参考下面的解决问题方案）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./Firefox.app/Contents/MacOS/firefox -profile /Volumes/&lt;PATH_TO_PROFILE&gt;</span><br></pre></td></tr></table></figure><p>所以，只需要指定好用户数据文件夹，就可以每次打开的时候都复用这个配置了，不用再手动设置了</p><p>另外，对于代理软件，我发现，直接跑软件的 Core 也是可以手动指定配置文件的，写好配置文件并配一下 Dashboard 就好了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./mihomo -d .</span><br></pre></td></tr></table></figure><p>经过这个方案，我就可以直接把需要的配置文件都放在 U 盘里了，每次使用只需要点开脚本就可以使用了</p><p>另外，因为 MacOS 的权限设置比较严格，所以我还需要在每次使用前，先打开一次 Terminal 来授权脚本的权限，比如：</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 解决 Operation not permitted</span></span><br><span class="line">xattr -rd com.apple.quarantine &lt;FILE_NAME&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 解决 File Broken</span></span><br><span class="line">xattr -cr &lt;FILE_NAME&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 解决 Permission denied</span></span><br><span class="line"><span class="built_in">chmod</span> +x &lt;FILE_NAME&gt;</span><br></pre></td></tr></table></figure><p>这些应该只用经过一次设置就可以了</p><p>然后，我用 AI 写了个脚本，点击就能自动打开代理软件的 Core 和 Firefox 开始上网了</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>虽然这个方案非常麻烦，但至少是可行的，满足了我在普通用户权限下使用网络代理的需求</p><p>归根结底，学校网络和中国的特殊网络环境确实让人头疼，作为普通人，只能用这种奇奇怪怪的方式来绕着弯子解决问题了</p><p>希望这个思路可以给有类似需求的人一些启发</p>]]>
    </content>
    <id>https://haotian22.top/7bd47711.html</id>
    <link href="https://haotian22.top/7bd47711.html"/>
    <published>2026-04-27T13:25:11.000Z</published>
    <summary>利用Firefox和代理软件的核心来实现普通用户权限下使用网络代理的方案</summary>
    <title>如何在MacOS的普通用户权限下使用网络代理</title>
    <updated>2026-04-27T13:25:11.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>近期，突发奇想，因为学校的地图并不太直观，要么细节数据不全，要么操作很难，需要点击多级才可以进入查看<br>因此，决定自己动手创建一份地图，方便日常使用</p><p>最后在询问了 AI 之后，决定使用 Tileserver-GL（后端底图）+ MapLiberGL（前端显示+POI叠加层）+ OSM（数据源） 来创建自己的地图</p><h2 id="部署-Tileserver-GL"><a href="#部署-Tileserver-GL" class="headerlink" title="部署 Tileserver-GL"></a>部署 Tileserver-GL</h2><h3 id="安装-Tileserver-GL"><a href="#安装-Tileserver-GL" class="headerlink" title="安装 Tileserver-GL"></a>安装 Tileserver-GL</h3><p>根据<a href="https://openmaptiles.org/docs/host/tileserver-gl/">官方的教程</a>, 使用 docker 来安装 Tileserver-GL 是最为便捷的</p><p>但是，官方并没有说明后期需要导入数据的时候应该如何操作，我在这里也踩了很多坑</p><p>最后，我根据自己的情况，写了一个 <code>docker-compose.yml</code>文件，内容如下，可以参考一下：</p><figure class="highlight yml"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">tileserver:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">maptiler/tileserver-gl</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8080:8080&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;/home/u0_a244/TileServer-GL/data:/data&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;/home/u0_a244/TileServer-GL/styles:/styles&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;/home/u0_a244/TileServer-GL/config.json:/config.json&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;/home/u0_a244/TileServer-GL/fonts:/fonts&quot;</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">&quot;--config&quot;</span>, <span class="string">&quot;/config.json&quot;</span>]</span><br></pre></td></tr></table></figure><p>其中，ports 部分是将容器的 8080 端口映射到宿主机的 8080 端口<br>volumes 部分是将宿主机的相关目录挂载到容器中</p><p>在我的环境中，&#x2F;home&#x2F;u0_a244&#x2F;TileServer-GL&#x2F; 是我用来存放 Tileserver-GL 相关文件的目录，数据如下：</p><figure class="highlight bash"><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">~/TileServer-GL$ tree -L 2</span><br><span class="line">.</span><br><span class="line">├── config.json</span><br><span class="line">├── data</span><br><span class="line">│   └── guangdong.mbtiles</span><br><span class="line">├── docker-compose.yml</span><br><span class="line">├── fonts</span><br><span class="line">│   └── MiSans Semibold</span><br><span class="line">└── styles</span><br><span class="line">    └── osm_liberty.json</span><br></pre></td></tr></table></figure><p>请根据自己的实际情况，修改 <code>docker-compose.yml</code> 中的路径</p><p>至于这些文件是怎么来的，后面会讲到</p><h2 id="修改配置文件"><a href="#修改配置文件" class="headerlink" title="修改配置文件"></a>修改配置文件</h2><p>在前面的 <code>docker-compose.yml</code> 文件中，我挂载了一个 <code>config.json</code> 文件，这个文件就是用来配置 Tileserver-GL 的配置</p><p>相关的文档可以在<a href="https://tileserver.readthedocs.io/en/latest/config.html">官网</a>找到</p><p>我这里使用的配置文件为：</p><figure class="highlight json"><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><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;options&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;paths&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;root&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;styles&quot;</span><span class="punctuation">:</span> <span class="string">&quot;styles&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;fonts&quot;</span><span class="punctuation">:</span> <span class="string">&quot;fonts&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;mbtiles&quot;</span><span class="punctuation">:</span> <span class="string">&quot;data&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;domains&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;my-domain:8080&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;formatOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;jpeg&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;quality&quot;</span><span class="punctuation">:</span> <span class="number">80</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;webp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;quality&quot;</span><span class="punctuation">:</span> <span class="number">90</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;maxScaleFactor&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;maxSize&quot;</span><span class="punctuation">:</span> <span class="number">2048</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;pbfAlias&quot;</span><span class="punctuation">:</span> <span class="string">&quot;pbf&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;serveAllFonts&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;serveAllStyles&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;serveStaticMaps&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allowRemoteMarkerIcons&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allowInlineMarkerImages&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;staticAttributionText&quot;</span><span class="punctuation">:</span> <span class="string">&quot;© OpenMapTiles  © OpenStreetMaps&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;tileMargin&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;styles&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;osm-liberty&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;osm_liberty.json&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;serve_rendered&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;remote&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;style&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://demotiles.maplibre.org/style.json&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;openmaptiles&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;mbtiles&quot;</span><span class="punctuation">:</span> <span class="string">&quot;guangdong.mbtiles&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这里重点需要修改的是 <code>domains</code>部分，将 <code>my-domain:8080</code> 修改为你自己的域名或者 IP 地址加端口号，否则无法正常请求，另外根据文档所述，还能在访问的时候达到负载均衡的效果（挂载到服务器用的时候绝对不能写成 localhost）</p><p>此外，<code>mbtiles</code> 部分需要修改为你自己的 mbtiles 文件名（因为我在文件上方已经说明了 <code>mbtiles</code> 是在 <code>data</code> 目录下的，所以就不用再写 <code>data/</code> 了）</p><p><code>styles</code> 部分是用来配置样式文件的，也需要改成自己的样式文件名，后面会讲到如何获取和修改样式文件</p><h2 id="获取-mbtiles-数据"><a href="#获取-mbtiles-数据" class="headerlink" title="获取 mbtiles 数据"></a>获取 mbtiles 数据</h2><p>Tileserver-GL 需要 mbtiles 格式的数据才能运行，获取 mbtiles 数据有很多种方式，这里介绍两种比较常用的方式</p><h3 id="方式一：BBBike-下载"><a href="#方式一：BBBike-下载" class="headerlink" title="方式一：BBBike 下载"></a>方式一：BBBike 下载</h3><p>BBBike 是一个提供地图数据下载的网站，支持多种格式的地图数据下载，包括 mbtiles 格式</p><ol><li>访问 <a href="https://extract.bbbike.org/">BBBike 官网</a></li><li>在地图上选择你需要的区域，可以通过搜索城市名称来快速定位</li><li>选择输出格式为 mbtiles</li><li>提交下载请求，填入邮箱，等待 10 分钟左右处理完成后下载 mbtiles 文件</li></ol><p>这个方式的优点是简单快捷，适合下载小范围的地图数据</p><p>但是因为地图的生成方式不是自己控制的，后续在数据处理和渲染的时候会遇到不少问题</p><p>这部分我会在渲染样式的时候讲到</p><h3 id="方式二：使用-MapTiler-下载"><a href="#方式二：使用-MapTiler-下载" class="headerlink" title="方式二：使用 MapTiler 下载"></a>方式二：使用 MapTiler 下载</h3><p>MapTiler 官方提供了数据下载，可以直接访问 <a href="https://www.maptiler.com/on-prem-datasets/dataset/osm/#0.22/0/0">MapTiler 数据下载页面</a>，选择区域下载即可</p><p>但是它的免费数据下载有一些限制：</p><ul><li>OpenStreetMap Vectors (2020)</li><li>Satellite Low-Res (2016)</li><li>Non-commercial</li></ul><p>反正我是觉得 2020 年的数据有点旧了，而且所以就没有选这个</p><h3 id="方式三：使用-OSM-数据-tilemaker-自己生成-mbtiles"><a href="#方式三：使用-OSM-数据-tilemaker-自己生成-mbtiles" class="headerlink" title="方式三：使用 OSM 数据 + tilemaker 自己生成 mbtiles"></a>方式三：使用 OSM 数据 + tilemaker 自己生成 mbtiles</h3><ol><li>下载 OSM 数据，可以从 <a href="https://download.geofabrik.de/">Geofabrik</a> 下载所需区域的 PBF 文件</li><li>安装 tilemaker，可以参考 <a href="https://github.com/systemed/tilemaker">tilemaker 的 GitHub 页面</a></li></ol><p>其实，如果并不在意程序的最新版本的话，可以直接在GitHub的 Releases 页面下载编译好的二进制文件但是需要注意的是，Windows 下的 v3.0.0 版本是有问题的，Windows用户 建议下载 v2.4.0 版本<br>另外，还需要下载配置文件 <code>config-openmaptiles.json</code> 和处理脚本 <code>process-openmaptiles.lua</code>，可以从该仓库的 <code>resources</code> 目录中获取</p><ol start="3"><li>使用 tilemaker 将 PBF 文件转换为 mbtiles 文件，实例命令如下：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tilemaker --input <span class="string">&quot;guangdong-260113.osm.pbf&quot;</span>  --output <span class="string">&quot;guangdong.mbtiles&quot;</span> --config .\config-openmaptiles.json --process .\process-openmaptiles.lua</span><br></pre></td></tr></table></figure><p>其中，<code>--input</code> 指定输入的 PBF 文件，<code>--output</code> 指定输出的 mbtiles 文件，<code>--config</code> 和 <code>--process</code> 分别指定配置文件和处理脚本，需要自己替换成实际的文件路径</p><p>如果想裁切数据，可以使用 <code>--bbox</code> 参数，格式为 <code>minLon,minLat,maxLon,maxLat</code>，例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tilemaker.exe --input <span class="string">&quot;guangdong-260113.osm.pbf&quot;</span> --bbox 113,22.34,114,22.50 --output <span class="string">&quot;guangdong.mbtiles&quot;</span> --config .\config-openmaptiles.json --process .\process-openmaptiles.lua</span><br></pre></td></tr></table></figure><p>还有值得注意的是，<code>process-openmaptiles.lua</code> 文件开头的几个带 <code>language</code> 的变量需要根据自己后面的 <code>style.json</code> 文件来决定，否则可能会导致某些标签无法显示，我后面会再次提到</p><p>然后打完这个命令之后，等待一段时间（我生成一个学校的大小用时大概为 30s, 使用 i7-12700H CPU，占用 5.5GB 内存），就可以得到一个 mbtiles 文件了</p><p>最后记得将它移动到 Tileserver-GL 的 data 目录下</p><p>但是这个方法也有一定的缺点（相对于 Maptiler 提供的文件来说），因为它经过一次转换，例如 building:part 之类的细节就会被去掉，没法准确的渲染出来 3D 效果</p><center><img src="https://s41.ax1x.com/2026/01/17/pZsWKE9.png" style="width:60%;" alt="tilemaker 生成的数据结果预览"/></center><p>另外，这个图也解释了为什么无法直接从平台 Geojson 变成 Tileserver-GL 样式渲染需要的格式：它是分为右侧那几大类来分类渲染，普通 Geojson 是没分类的，会导致无法识别类型而对应不上样式</p><p>至于有没有方法可以从普通的在 Overpass 上获得的 Geojson 转化为这个分类的格式，再用 osmium-tool 之类的转为 mbtiles呢？或许是有机会的，但是我似乎没有找到，如果有找到的话可以在评论区提一下</p><h3 id="方法四：使用-OpenMapTiles-构建完整数据集"><a href="#方法四：使用-OpenMapTiles-构建完整数据集" class="headerlink" title="方法四：使用 OpenMapTiles 构建完整数据集"></a>方法四：使用 OpenMapTiles 构建完整数据集</h3><p>这个是官方推荐的方法，使用 OpenMapTiles 工具链来生成完整的地图数据集，但是部署的文件相当复杂，会变得非常慢</p><p>可以参考官方 <a href="https://github.com/openmaptiles/openmaptiles">GitHub 仓库</a>的 README 文档来进行操作</p><p>如果需要裁切之前的整个 PBF 为特定区域的数据，可以使用 <a href="https://wiki.openstreetmap.org/wiki/Osmconvert">OSMconvert</a> 来操作，链接里面直接下载二进制文件就行了，然后使用以下命令来裁切数据，经纬度可以使用 <a href="https://boundingbox.klokantech.com/">Bounding Box Tool</a> 来获取</p><p>（这是我在 Windows 下的命令，其他系统请自行参照文档更改）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.\osmconvert64-0.8.8p.exe .\guangdong-260113.osm.pbf -b=<span class="string">&quot;113.4,22.3,114.5,22.6&quot;</span> -o=<span class="string">&quot;guangdong.osm.pbf&quot;</span></span><br></pre></td></tr></table></figure><p>然后将下载好的 pbf 放进 OpenMapTiles 的 <code>data</code> 目录下，根据文档走一次，就可以生成一个完整的 mbtiles 文件了</p><p>以下内容截取自 GitHub 仓库的 README 文档，有些我标了 optional 的没有需要就可以跳过：</p><figure class="highlight bash"><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">make clean                  <span class="comment"># clean / remove existing build files</span></span><br><span class="line">make                        <span class="comment"># generate build files</span></span><br><span class="line">make start-db               <span class="comment"># start up the database container.</span></span><br><span class="line">make import-data            <span class="comment"># Import external data from OpenStreetMapData, Natural Earth and OpenStreetMap Lake Labels.</span></span><br><span class="line">make download area=albania  <span class="comment"># download albania .osm.pbf file -- can be skipped if a .osm.pbf file already existing (optional)</span></span><br><span class="line">make import-osm             <span class="comment"># import data into postgres</span></span><br><span class="line">make import-wikidata        <span class="comment"># import Wikidata (optional)</span></span><br><span class="line">make import-sql             <span class="comment"># create / import sql functions </span></span><br><span class="line">make generate-bbox-file     <span class="comment"># compute data bbox -- not needed for the whole planet or for downloaded area by `make download`</span></span><br><span class="line">make generate-tiles-pg      <span class="comment"># generate tiles</span></span><br></pre></td></tr></table></figure><p>实测以我老笔记本 i7-2677M + SSD 的配置，生成一个学校的范围就部署了将近 5 个小时（因为主力机没装 Linux，没法跑 make）<br>主要卡在 SSD 的 I&#x2F;O 写入瓶颈上，因为总共会生成多个 Docker Container，产生大量的写入<br>建议使用高速的 M.2 NVMe SSD 来进行操作</p><p>第二次跑似乎就快点了，0.05 的经度和 0.03 的纬度范围大概跑了 20 分钟，跑出来的文件大小在 500KB 左右</p><p>然后我又试了下我 Azure 的 1 年免费 VPS（2C1G + 64G Premium SSD，开了 2G 的 Swap），跑了就大概 5 分钟，读写平均上到了 70MB&#x2F;s，可以根据自己的硬件情况来预估时间</p><p>另外服务器上部署要注意的是，HDD 的服务器几乎完全不能部署，性能都不太行，跑几分钟还不够我笔记本上跑几秒</p><center><img src="https://s41.ax1x.com/2026/01/17/pZsWEcT.png" style="width:60%;" alt="OpenMapTiles 的数据结果预览"/></center><h2 id="获取-Tileserver-GL-渲染样式文件"><a href="#获取-Tileserver-GL-渲染样式文件" class="headerlink" title="获取 Tileserver-GL 渲染样式文件"></a>获取 Tileserver-GL 渲染样式文件</h2><p>虽然默认直接启动，它是会自动加载自带的样式文件的，但是因为调整配置不方便，而且并不是很好看，我就选择了使用其他的 style 文件</p><p>我是在 <a href="https://maplibre.org/maputnik/">Maputnik</a> 上的左上角找到了一个叫做 OSM Liberty 的样式文件，预览后觉得还可以，就点击左上角的 Save 按钮下载</p><p>但是，由于各个文件的渲染逻辑不同，为了不渲染出现问题，需要对样式文件进行一些修改</p><h3 id="修改名称读取"><a href="#修改名称读取" class="headerlink" title="修改名称读取"></a>修改名称读取</h3><p>打开下载下来的 <code>osm-liberty.json</code> 文件，<code>ctrl+f</code>找到 <code>name</code> 部分，将某些的 <code>name:latin</code> 进行修改</p><p>具体是怎么改，还是要看回去上一节中 <code>process-openmaptiles.lua</code> 文件中 <code>language</code> 变量的设置</p><p>比如我在上面的 lua 设置的是：</p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- Preferred language can be (for example) &quot;en&quot; for English, &quot;de&quot; for German, or nil to use OSM&#x27;s name tag:</span></span><br><span class="line">preferred_language = <span class="literal">nil</span></span><br><span class="line"><span class="comment">-- This is written into the following vector tile attribute (usually &quot;name:latin&quot;)</span></span><br><span class="line">preferred_language_attribute = <span class="string">&quot;name&quot;</span></span><br><span class="line"><span class="comment">-- If OSM&#x27;s name tag differs, then write it into this attribute (usually &quot;name_int&quot;):</span></span><br><span class="line">default_language_attribute = <span class="string">&quot;name_int&quot;</span></span><br><span class="line"><span class="comment">-- Also write these languages if they differ - for example, &#123; &quot;de&quot;, &quot;fr&quot; &#125;</span></span><br><span class="line">additional_languages = &#123;<span class="string">&quot;zh&quot;</span>,<span class="string">&quot;en&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>这个设置就决定在前面生成 mbtiles 的时候，将 OpenStreetMap 中的 <code>name</code> 标签写入 <code>name</code> 属性中，<code>name:zh</code> 标签写入 <code>name:zh</code> 属性中，<code>name:en</code> 标签写入 <code>name:en</code> 属性中 （理论上是，但是不知道为什么我生成的文件中并没有 <code>name:zh</code> 标签）</p><p>我这样改是为了避免歧义，反正默认操作会在生成 mbtiles 的时候把中文写入 <code>name:latin</code>标签，听起来逻辑怪怪的</p><p>所以，我就应该把全部 <code>name:latin</code> 改成 <code>name</code>，因为生成的文件根本就没有 <code>name:latin</code> 标签</p><p>另外，如果不是用 maputnik 转换生成文件的话（即不是用上面的方法3），具体应该写什么可以先跳过这部分，先启动一次 Tileserver-GL，在网页下方的 Data 选项卡的 Inspect 按钮，点进去随机看几个 POI，看文件里面究竟写了什么字段</p><p>示例：</p><center><img src="https://s41.ax1x.com/2026/01/17/pZsRf1K.png" style="width:30%;" alt="使用方法 4 OpenMapTiles 构建的数据"/></center><p>如上图，我写的就是下面这样，来同时支持中文和英文的显示</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;text-field&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">          <span class="string">&quot;case&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;all&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="punctuation">[</span><span class="string">&quot;has&quot;</span><span class="punctuation">,</span> <span class="string">&quot;name:latin&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="punctuation">[</span><span class="string">&quot;!=&quot;</span><span class="punctuation">,</span> <span class="punctuation">[</span><span class="string">&quot;get&quot;</span><span class="punctuation">,</span> <span class="string">&quot;name&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="punctuation">[</span><span class="string">&quot;get&quot;</span><span class="punctuation">,</span> <span class="string">&quot;name:latin&quot;</span><span class="punctuation">]</span><span class="punctuation">]</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;format&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="punctuation">[</span><span class="string">&quot;get&quot;</span><span class="punctuation">,</span> <span class="string">&quot;name&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;\n&quot;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="punctuation">[</span><span class="string">&quot;get&quot;</span><span class="punctuation">,</span> <span class="string">&quot;name:latin&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;font-scale&quot;</span><span class="punctuation">:</span> <span class="number">0.8</span> <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="punctuation">[</span><span class="string">&quot;get&quot;</span><span class="punctuation">,</span> <span class="string">&quot;name&quot;</span><span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">]</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><h3 id="修改地图路径"><a href="#修改地图路径" class="headerlink" title="修改地图路径"></a>修改地图路径</h3><p>在样式文件中，通常都会直接指定地图 tiles 的路径，但是这个也可以通过引用 config.json 文件中的数据源来读取</p><p>比如我就写成了</p><figure class="highlight json"><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"><span class="attr">&quot;sources&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;openmaptiles&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vector&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mbtiles://&#123;openmaptiles&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;attribution&quot;</span><span class="punctuation">:</span> <span class="string">&quot;© OpenStreetMap contributors&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这样就可以直接引用 <code>config.json</code> 文件中 <code>data</code> 部分的 <code>openmaptiles</code> 数据源了</p><p>如果你的 <code>config.json</code> 文件中 <code>data</code> 部分的名称不是 <code>openmaptiles</code> 的话，需要相应地修改这里的名称</p><h3 id="修改字体"><a href="#修改字体" class="headerlink" title="修改字体"></a>修改字体</h3><p>默认字体是无法显示中文的，所以需要修改字体</p><p>首先是下载字体，我这里使用的是 MiSans ，先获取到字体的 ttf 或者 otf 文件</p><p>然后，需要用特殊的工具将字体转换为 PBF 格式</p><p>可以使用 <a href="https://maplibre.org/font-maker/">font-maker</a> 这个网站来转换</p><p>也可以使用 <a href="https://github.com/openmaptiles/fonts">fonts</a> 这个工具来转换</p><p>效果应该都差不多的</p><p>转换完成后，将生成的存有 pbf 文件夹整个放到 Tileserver-GL 的 fonts 目录下</p><p>最后，修改样式文件中的字体引用，找到以下位置修改：</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="attr">&quot;glyphs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;fontstack&#125;/&#123;range&#125;.pbf&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>此外，还需要搜索整个文件，找到带有 <code>text-font</code> 的地方，将字体名称修改为你刚才转换的字体文件夹的名称</p><p>例如我写的是 <code>&quot;text-font&quot;: [&quot;MiSans Semibold&quot;],</code></p><h2 id="启动-Tileserver-GL"><a href="#启动-Tileserver-GL" class="headerlink" title="启动 Tileserver-GL"></a>启动 Tileserver-GL</h2><p>完成以上步骤后，就可以启动 Tileserver-GL 了<br>在 Tileserver-GL 目录下，运行以下命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br></pre></td></tr></table></figure><p>然后，打开浏览器，访问 <code>http://your-domain:8080</code> （将 <code>your-domain</code> 替换为你的域名或者 IP 地址），就可以看到自己的地图了</p><center><img src="https://s41.ax1x.com/2026/01/17/pZsWQ41.jpg" style="width:60%;" alt="Tileserver-GL + Openmaptiles + OSM Liberty 最终渲染效果"/></center><h2 id="创建前端-html-页面"><a href="#创建前端-html-页面" class="headerlink" title="创建前端 html 页面"></a>创建前端 html 页面</h2><p>最后，如果想要创建一个前端的 html 页面来展示地图，可以参考以下代码：</p><figure class="highlight html"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>My Map<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">href</span>=<span class="string">&quot;https://unpkg.com/maplibre-gl@2.1.9/dist/maplibre-gl.css&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">        <span class="selector-tag">body</span> &#123; <span class="attribute">margin</span>: <span class="number">0</span>; <span class="attribute">padding</span>: <span class="number">0</span>; &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-id">#map</span> &#123; <span class="attribute">position</span>: absolute; <span class="attribute">top</span>: <span class="number">0</span>; <span class="attribute">bottom</span>: <span class="number">0</span>; <span class="attribute">width</span>: <span class="number">100%</span>; &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;map&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://unpkg.com/maplibre-gl@2.1.9/dist/maplibre-gl.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> map = <span class="keyword">new</span> maplibregl.<span class="title class_">Map</span>(&#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">container</span>: <span class="string">&#x27;map&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">style</span>: <span class="string">&#x27;http://your-domain:8080/styles/osm-liberty/style.json&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">center</span>: [<span class="number">113.5</span>, <span class="number">22.4</span>],</span></span><br><span class="line"><span class="language-javascript">            <span class="attr">zoom</span>: <span class="number">12</span></span></span><br><span class="line"><span class="language-javascript">        &#125;);</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span>   </span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这里的 <code>style</code> 地址需要修改为你自己的 Tileserver-GL 的样式地址，建议使用 https 来避免跨域问题</p><p>后面再在上方加 POI 标记、自定义画线什么的，参照 <a href="https://maplibre.org/maplibre-gl-js-docs/">MapLibre GL JS 官方文档</a> 就可以了，或者问 AI 也行，实测错误率不高</p><p>反正我后面使用的 Overpass API 来获取 POI 数据，然后使用 MapLibre GL JS 的 Marker 功能来渲染这些数据，效果还不错</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>以上就是使用 Tileserver-GL + MapLiberGL + OSM 创建自己的地图的详细教程<br>希望对大家有所帮助，如果有任何问题，欢迎在评论区留言讨论！</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://tileserver.readthedocs.io/">Tileserver-GL 官方文档</a></li><li><a href="https://maplibre.org/maplibre-gl-js/docs">MapLibre GL JS 官方文档</a></li><li><a href="https://docs.mapbox.com/style-spec/reference/expressions/">Expressions | Mapbox Style Spec | Mapbox Docs | Mapbox</a></li><li><a href="https://openmaptiles.org/docs/">OpenMapTiles 官方文档</a></li><li><a href="https://github.com/openmaptiles/openmaptiles">OpenMapTiles GitHub</a></li><li><a href="https://blog.sparktour.me/posts/2021/08/03/self-host-openstreetmap-tiles-server/">自建OpenStreetmap地图瓦片服务 | Sparktour’s Blog</a></li><li><a href="https://github.com/mapbox/tippecanoe/">Tippecanoe GitHub</a> (用来 GeoJSON 生成 mbtiles 的，但是因为 layer 样式对不上用不了)</li><li><a href="https://juejin.cn/post/7371423076661231656">简单地图应用-开源地图OpenStreetMap仿Mapbox样式部署本地矢量瓦片（上）一、引言近期在实验性质的做自己 - 掘金</a></li><li><a href="https://openmaptiles.org/docs/generate/create-custom-extract/">Create custom map extract - OpenMapTiles</a></li><li><a href="https://wiki.openstreetmap.org/wiki/Osmconvert">Osmconvert - OpenStreetMap Wiki</a> (OSMconvert 用来裁切 PBF 数据)</li><li><a href="https://gis.stackexchange.com/questions/185927/how-to-clip-a-osm-pbf-file-to-a-shapefile-in-r">openstreetmap - How to clip a .osm.pbf file to a shapefile in R - Geographic Information Systems Stack Exchange</a></li><li><a href="https://extract.bbbike.org/">BBBike extracts OpenStreetMap (OSM,Garmin,Shapefile etc.)</a></li><li><a href="https://maplibre.org/maputnik/">Maputnik</a> (在线样式编辑器)</li><li><a href="https://maplibre.org/font-maker/">font-maker</a> (在线字体转换工具)</li></ul>]]>
    </content>
    <id>https://haotian22.top/bddb0203.html</id>
    <link href="https://haotian22.top/bddb0203.html"/>
    <published>2026-01-16T03:11:55.000Z</published>
    <summary>使用Tileserver-GL+MapLiberGL+OSM创建自己的地图的详细教程，包括部署Tileserver-GL、获取mbtiles数据、配置样式文件以及使用MapLibre GL JS进行地图渲染。</summary>
    <title>使用Tileserver-GL+MapLiberGL+OSM创建自己的地图</title>
    <updated>2026-01-17T07:55:47.563Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>今天，突然受到了一封邮件，内容是 LeanCloud 宣布将于 2027 年 1 月关闭其数据库服务。<br>我点进去看了一下，发现是真的。<br><a href="https://console.leancloud.app/docs/sdk/announcements/sunset-announcement">LeanCloud 关闭数据库服务通知</a><br>毕竟，我博客上评论区、统计阅读量之类的数据都放在了 LeanCloud 上，所以这件事对我来说还是挺重要的。</p><p>然后，我就上了 <a href="https://waline.js.org/en/guide/database.html">Waline</a> 的官网，发现 Waline 支持 TiDB 作为后端数据库，而且看起来免费额度还挺大，教程也是第一个推荐这个，于是就选了它</p><h2 id="创建-TiDB-数据库"><a href="#创建-TiDB-数据库" class="headerlink" title="创建 TiDB 数据库"></a>创建 TiDB 数据库</h2><p>第一步，当然是跟着<a href="https://waline.js.org/en/guide/deploy/tidb.html#create-database">官方教程</a>走，创建数据库。不过，因为官方的文档实在是太旧了，UI界面什么的完全不一样，因此我还花了不少时间摸索了一下。总之，和官方的教程差异如下：</p><ul><li><code>Chat2Query</code>变成了 <code>SQL Editor</code></li><li>链接数据库的时候选择 <code>General</code>选项就好了，里面的几个信息分别填入 Vercel 的 <code>TIDB_HOST</code>、<code>TIDB_USER</code>、<code>TIDB_PASSWORD</code>、<code>TIDB_DB</code>环境变量</li></ul><h2 id="导出-LeanCloud-数据"><a href="#导出-LeanCloud-数据" class="headerlink" title="导出 LeanCloud 数据"></a>导出 LeanCloud 数据</h2><p>接下来，就是导出 LeanCloud 的数据了。<br>LeanCloud 提供了数据导出的功能，根据停运公告的教程，我过了 20 分钟左右就收到了包含我数据库所有内容的邮件</p><p>不过导出的数据是 JSONL 格式的，而 TiDB 需要的是 CSV (150MB 以下) 格式的，因此还需要进行一些转换。</p><p>我最终让 Copilot 帮我写了一个 Python 脚本来完成这个任务，代码如下：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> csv</span><br><span class="line"></span><br><span class="line">input_file = <span class="string">&quot;input.jsonl&quot;</span></span><br><span class="line">output_file = <span class="string">&quot;output.csv&quot;</span></span><br><span class="line"></span><br><span class="line">rows = []</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取 JSONL</span></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(input_file, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    <span class="keyword">for</span> line <span class="keyword">in</span> f:</span><br><span class="line">        <span class="keyword">if</span> line.strip():</span><br><span class="line">            rows.append(json.loads(line))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 自动收集所有字段作为表头</span></span><br><span class="line">headers = <span class="built_in">set</span>()</span><br><span class="line"><span class="keyword">for</span> r <span class="keyword">in</span> rows:</span><br><span class="line">    headers.update(r.keys())</span><br><span class="line">headers = <span class="built_in">list</span>(headers)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 写入 CSV</span></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(output_file, <span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    writer = csv.DictWriter(f, fieldnames=headers)</span><br><span class="line">    writer.writeheader()</span><br><span class="line">    writer.writerows(rows)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Done!&quot;</span>)</span><br></pre></td></tr></table></figure><p>看起来还是挺不错的</p><h2 id="导入旧数据到-TiDB"><a href="#导入旧数据到-TiDB" class="headerlink" title="导入旧数据到 TiDB"></a>导入旧数据到 TiDB</h2><p>因为原有的 Leancloud 转换出来的 CSV 字段和 TiDB 的表结构并不完全匹配，所以还需要对 CSV 进行一些手动的修改，主要是删除一些多余的字段，和调整字段顺序。<br>我于是又让他帮我写了下面这个脚本：</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><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><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> csv</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> datetime <span class="keyword">import</span> datetime</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">parse_parse_date</span>(<span class="params">value</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> value:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">r&quot;\N&quot;</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        obj = json.loads(value.replace(<span class="string">&quot;&#x27;&quot;</span>, <span class="string">&#x27;&quot;&#x27;</span>))</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">isinstance</span>(obj, <span class="built_in">dict</span>) <span class="keyword">and</span> obj.get(<span class="string">&quot;__type&quot;</span>) == <span class="string">&quot;Date&quot;</span>:</span><br><span class="line">            iso = obj.get(<span class="string">&quot;iso&quot;</span>)</span><br><span class="line">            dt = datetime.fromisoformat(iso.replace(<span class="string">&quot;Z&quot;</span>, <span class="string">&quot;+00:00&quot;</span>))</span><br><span class="line">            <span class="keyword">return</span> dt.strftime(<span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>)</span><br><span class="line">    <span class="keyword">except</span>:</span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line">    <span class="keyword">return</span> value <span class="keyword">or</span> <span class="string">r&quot;\N&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">to_int_or_null</span>(<span class="params">value</span>):</span><br><span class="line">    <span class="keyword">if</span> value <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="string">r&quot;\N&quot;</span></span><br><span class="line">    value = <span class="built_in">str</span>(value).strip()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Checking value: &#x27;<span class="subst">&#123;value&#125;</span>&#x27;&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> value.isdigit():</span><br><span class="line">        <span class="built_in">print</span>(<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">return</span> value</span><br><span class="line">    <span class="keyword">return</span> <span class="string">r&quot;\N&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">to_str_or_null</span>(<span class="params">value</span>):</span><br><span class="line">    <span class="keyword">if</span> value <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">r&quot;&quot;</span></span><br><span class="line">    v = <span class="built_in">str</span>(value).strip()</span><br><span class="line">    <span class="keyword">return</span> v <span class="keyword">if</span> v <span class="keyword">else</span> <span class="string">r&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">nullify</span>(<span class="params">value</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;把空字符串、None、空白都变成 \\N&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">if</span> value <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">r&quot;\N&quot;</span></span><br><span class="line">    v = <span class="built_in">str</span>(value).strip()</span><br><span class="line">    <span class="keyword">return</span> v <span class="keyword">if</span> v <span class="keyword">else</span> <span class="string">r&quot;\N&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">input_file = <span class="string">&quot;output.csv&quot;</span></span><br><span class="line">output_file = <span class="string">&quot;db_ready.csv&quot;</span></span><br><span class="line"></span><br><span class="line">target_fields = [</span><br><span class="line">    <span class="string">&quot;id&quot;</span>, <span class="string">&quot;user_id&quot;</span>, <span class="string">&quot;comment&quot;</span>, <span class="string">&quot;insertedAt&quot;</span>, <span class="string">&quot;ip&quot;</span>, <span class="string">&quot;link&quot;</span>, <span class="string">&quot;mail&quot;</span>, <span class="string">&quot;nick&quot;</span>,</span><br><span class="line">    <span class="string">&quot;pid&quot;</span>, <span class="string">&quot;rid&quot;</span>, <span class="string">&quot;sticky&quot;</span>, <span class="string">&quot;status&quot;</span>, <span class="string">&quot;like&quot;</span>, <span class="string">&quot;ua&quot;</span>, <span class="string">&quot;url&quot;</span>, <span class="string">&quot;createdAt&quot;</span>, <span class="string">&quot;updatedAt&quot;</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取所有行</span></span><br><span class="line">rows = []</span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(input_file, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    reader = csv.DictReader(f)</span><br><span class="line">    <span class="keyword">for</span> row <span class="keyword">in</span> reader:</span><br><span class="line">        rows.append(row)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立 objectId → new_id 映射</span></span><br><span class="line">objectId_to_newId = &#123;row[<span class="string">&quot;objectId&quot;</span>]: i <span class="keyword">for</span> i, row <span class="keyword">in</span> <span class="built_in">enumerate</span>(rows)&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 生成新 CSV 数据</span></span><br><span class="line">output_rows = []</span><br><span class="line"><span class="keyword">for</span> row <span class="keyword">in</span> rows:</span><br><span class="line">    new_row = &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 新 id</span></span><br><span class="line">    new_row[<span class="string">&quot;id&quot;</span>] = objectId_to_newId[row[<span class="string">&quot;objectId&quot;</span>]]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># user_id</span></span><br><span class="line">    new_row[<span class="string">&quot;user_id&quot;</span>] = to_int_or_null(row.get(<span class="string">&quot;user_id&quot;</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 普通字段</span></span><br><span class="line">    <span class="keyword">for</span> field <span class="keyword">in</span> [<span class="string">&quot;comment&quot;</span>, <span class="string">&quot;link&quot;</span>, <span class="string">&quot;mail&quot;</span>, <span class="string">&quot;nick&quot;</span>, <span class="string">&quot;sticky&quot;</span>, <span class="string">&quot;like&quot;</span>, <span class="string">&quot;ua&quot;</span>, <span class="string">&quot;url&quot;</span>]:</span><br><span class="line">        new_row[field] = nullify(row.get(field, <span class="string">&quot;&quot;</span>))</span><br><span class="line">  </span><br><span class="line">    <span class="comment"># status and ip use empty string as default (not \N)</span></span><br><span class="line">    new_row[<span class="string">&quot;status&quot;</span>] = to_str_or_null(row.get(<span class="string">&quot;status&quot;</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line">    new_row[<span class="string">&quot;ip&quot;</span>] = to_str_or_null(row.get(<span class="string">&quot;ip&quot;</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 时间字段</span></span><br><span class="line">    new_row[<span class="string">&quot;insertedAt&quot;</span>] = parse_parse_date(row.get(<span class="string">&quot;insertedAt&quot;</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line">    new_row[<span class="string">&quot;createdAt&quot;</span>]  = parse_parse_date(row.get(<span class="string">&quot;createdAt&quot;</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line">    new_row[<span class="string">&quot;updatedAt&quot;</span>]  = parse_parse_date(row.get(<span class="string">&quot;updatedAt&quot;</span>, <span class="string">&quot;&quot;</span>))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># pid 映射</span></span><br><span class="line">    old_pid = row.get(<span class="string">&quot;pid&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    new_row[<span class="string">&quot;pid&quot;</span>] = objectId_to_newId.get(old_pid, <span class="string">r&quot;\N&quot;</span>) <span class="keyword">if</span> old_pid <span class="keyword">else</span> <span class="string">r&quot;\N&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># rid 映射</span></span><br><span class="line">    old_rid = row.get(<span class="string">&quot;rid&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    new_row[<span class="string">&quot;rid&quot;</span>] = objectId_to_newId.get(old_rid, <span class="string">r&quot;\N&quot;</span>) <span class="keyword">if</span> old_rid <span class="keyword">else</span> <span class="string">r&quot;\N&quot;</span></span><br><span class="line"></span><br><span class="line">    output_rows.append(new_row)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 写入 CSV</span></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(output_file, <span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    writer = csv.DictWriter(f, fieldnames=target_fields)</span><br><span class="line">    writer.writeheader()</span><br><span class="line">    writer.writerows(output_rows)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;完成：所有空值已转换为 \\N，适用于 TiDB Lightning 导入。&quot;</span>)</span><br></pre></td></tr></table></figure><p>这段代码可以自动将已有的 ObjectId 转换为 TiDB 需要的递增 id ，自动将 <code>pid</code>和 <code>rid</code>等需要互相对应的值迁移到新的 id 结构里，将空值填上 <code>\N</code>或者空字符串。</p><p>不过，因为<strong>用户数据</strong>并不在这个 Comment 数据表里面，需要对应回去的话需要<strong>手动</strong>查查 User 的数据表迁移后的各个用户对应 id 再手动替换一下（但是一般来说都没几个人会专门注册才来评论，反正我这里就我一个，因此我就只需要简单的 Ctrl+H 替换自己的 id 就行了）。</p><p>在迁移过程中，我踩过最大的坑应该就是空值的问题了<br>前期，我一直以为直接在 CSV 里留空就行，结果导入 TiDB 的时候各种报错，后来才发现 TiDB Lightning 要求空值必须是 <code>\N</code>才行，于是我加上了 <code>nullify</code>函数来处理这些空值。</p><p>不过，总算是顺利完成了数据的迁移工作。<br>最后，用起来感觉的话，似乎 TiDB 的网络链接会比 LeanCloud 要慢一点？查表的时候要等待好一会才能出来，加载博客评论的时间似乎也比 LeanCloud 要长一些。</p>]]>
    </content>
    <id>https://haotian22.top/5d6475bb.html</id>
    <link href="https://haotian22.top/5d6475bb.html"/>
    <published>2026-01-12T16:06:38.000Z</published>
    <summary>本文记录了我将博客评论系统从LeanCloud迁移到TiDB的全过程，包括创建TiDB数据库、导出LeanCloud数据、转换数据格式以及导入TiDB的步骤和遇到的问题。</summary>
    <title>迁移LeanCloud到TiDB</title>
    <updated>2026-01-26T00:07:30.747Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="生活" scheme="https://haotian22.top/tags/%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>来到香港生活后，因为开了 Android 八达通，平时经常需要用到Google Wallet来支付食堂和交通费，但是，由于银联给的89.9的汇率实在是有点香（相对比市场价91.7），因此又挺希望用手机自带的钱包Pay来银联NFC&#x2F;二维码付款的。</p><img src="https://s2.loli.net/2025/09/06/lTebpz6wcAIQJrZ.jpg" style="width:30%;" alt="银联手机Pay的超绝优惠汇率"/><p>然而，由于 Android 的限制，手机的默认付款 App 只能设置为一个默认的，此外再无更多的选项，造成了多个付款 App 之间会有冲突。</p><p>需要频繁切换的情况下，每次都需要打开设置，经过几次点击找到更换默认NFC付款的选项，确实有点麻烦了，尤其是在排队给钱的时候，一通操作下来少说也得5秒，会显得非常尴尬</p><p>于是，正当我烦恼的时候，打开手机钱包时候的“设置为默认NFC”的选项引起了我的注意，我就开始在想，有没有第三方的 App 能够把这个过程做到自动化，点一下按钮就能自动从两个或者更多保存过的 NFC 付款应用之间来切换，甚至能做到根据位置或使用习惯等自动切换呢？</p><p>然而，在 Google&#x2F;小红书&#x2F;Bilibili 等搜了一轮之后，结果却——基本没有Orz</p><p>但是，我又想到了 Android 有可以直接调用 Activtity 来快速打开页面的特性，于是向着这个方向找了找，果然还是找到了一点思路</p><p>最终，我现在的解决方案是：</p><p>利用<a href="https://github.com/sdex/ActivityManager">ActivityManager</a>，在里面找到<code>设置/Settings</code>（指的是系统设置的App）</p><p>再搜索<code>payment</code>，会发现一个叫<code>Settings$PaymentSettingsActivity Contactless payments</code>的选项</p><p>点击旁边的三个点，选择<code>Create Shortut</code>的选项，就会在桌面出来一个切换NFC付款程序的入口了</p><p>以后点击一下那个图标，就会出现如下图所示的这个选择框来切换</p><img src="https://s2.loli.net/2025/09/06/rF5678ExcNfbzdL.jpg" style="width:30%;" alt="NFC付款App选择框"/><p>虽然这并不是一个特别好的方式，但是至少，切换起来还是比进入设置里面一步步找要方便不少了</p><p>还有的教程会建议用Tasker，我看了看，其实也相差不大，但是那个可以自动化得更加彻底，例如设定在某个时间&#x2F;地点自动弹出选择NFC付款程序的框（指上面图里那个框，然而依然不能自动设置默认付款程序，还是得要手动点那么一下），所以也就没研究下去了</p><p>如果有更好的方式，欢迎在评论区提出</p><p><strong>后续研究</strong></p><p>后面我又用了几天，似乎发现NFC付款程序并不完全冲突，至少Type-F的八达通和Type-A的工商银行云闪付是可以共存的。</p><p>在一次测试中，我把默认付款应用设为八达通，保持八达通和工商银行的后台开启（通知栏显示Active的通知），然后贴了下八达通付款机是能直接给的</p><p>之后我还会继续测试更多的情况</p>]]>
    </content>
    <id>https://haotian22.top/4b516b74.html</id>
    <link href="https://haotian22.top/4b516b74.html"/>
    <published>2025-09-06T15:29:15.894Z</published>
    <summary>在海外使用手机时遇到的Google Pay与其他NFC付款App冲突与切换问题，并尝试通过Android Activity特性来快速切换NFC默认付款程序来解决的过程</summary>
    <title>Google Pay与其他NFC付款App的共存与切换</title>
    <updated>2025-09-06T15:29:16.232Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="随笔" scheme="https://haotian22.top/tags/%E9%9A%8F%E7%AC%94/"/>
    <category term="生活" scheme="https://haotian22.top/tags/%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>今天，大概是我今年在中国境内大学上课，或许也是我今年在这个城市的最后一天了。</p><p>收拾行李的时候，各种复杂的情感涌向脑海中，让我预期中收拾东西的时间变得漫长。不是因为东西太多，而是因为情绪太重。每一次弯腰、每一次翻找，仿佛不是在收拾物品，而是在和一段时光告别。当我终于拖着行李离开时，那种说不清的情感——不舍、焦虑、回忆的冲撞感，像潮水一样漫上来，压得人想哭。</p><img src="https://s21.ax1x.com/2025/06/19/pVVAFoV.md.jpg" alt="pVVAFoV.md.jpg" border="0" style="zoom:70%;"><p>我这种感觉的起因，很大程度上源于即将前往境外交换的决定。尽管因为嫌麻烦，或是对未来新室友的不确定性感到焦虑，我最终还是决定保留宿舍，并留下了一些个人物品。但这，反而也加剧了我的纠结：哪些该带走，哪些可以留？因为一旦离开，至少半年内我都不会回来。我今天的每一个判断错误，都将要留给未来半年后的我来承担。</p><p>于是，这次的“收拾”不再是一个简单的动作，而像是生活的一道分水岭：它把过去的我——在大学的生活、习惯、情绪一刀切开，连通了一个我无法完全想象的未来旅程。在这种旧时期已终结、新阶段尚未开启的过渡时刻，我感到一种强烈的“存在的空隙”：我还没从过去完全抽身，却也尚未真正踏入未来。这种悬在半空的状态，让我格外不安。</p><p>我还记得去年秋天返校时打开宿舍门的那一瞬间：霉菌爬满桌面，黑色的斑点布满墙壁和天花板，甚至桌角还有虫子爬动。曾经崭新的床边缘也锈迹斑斑，那些鞋子、衣物、抽屉里残留的潮气，无声地侵蚀着我对宿舍的信任。即使这次我吸取了教训，放了除湿剂，也还是担心这点准备根本撑不过两个假期。我很怕，回来之后看到的，是自己用心维系的小小生活空间，被霉和潮气吞没。无论那些物品是便宜的，还是我格外珍视的，都可能要被“重置”——我必须亲手清理、舍弃，哪怕它们陪我走过了一个时段的人生。</p><p>我这种焦虑，其实和我性格中的“对自己没信心”紧密相关。我常常在做事时反复思考：我有没有遗漏什么？这决定对吗？有些东西，当下觉得不重要，但真的找不到的时候，会无比烦躁。就因为这种性格，我会反复在手上拿着一个物品，思前想后，迟迟无法决定要不要带走。</p><p>还有一种莫名的感受，是在浏览宿舍物品时被记忆撞了一下的那种感觉——那些从活动中拿来的纪念品，也许在别人眼里不值一提，但我知道它们承载着一个个鲜活的瞬间。丢弃它们，就像在主动擦掉一些回忆。我甚至会担心，等我以后想起那段日子时，会变得模糊，甚至一无所获。</p><p>而最让我情绪复杂的，也许是我终于意识到——我真的住在这个空间里生活了好几个月。我用它、整理它、逐渐习惯它，把它变成我的一个“小基地”。即使它并不完美，即使它只是临时住所，但我确实在这里，经历了生活。而现在，我要亲手拆掉这个“基地”，和这段生活正式告别。那种割舍的感觉，比我想象得还要真切。</p><img src="https://s21.ax1x.com/2025/06/19/pVVAid0.md.jpg" alt="pVVAid0.md.jpg" border="0" style="zoom:80%;"><p>就这样，带着各种复杂的心情，我拖着箱子，离开了宿舍……</p>]]>
    </content>
    <id>https://haotian22.top/ec3c92ec.html</id>
    <link href="https://haotian22.top/ec3c92ec.html"/>
    <published>2025-06-18T17:06:00.301Z</published>
    <summary>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla]]>
    </summary>
    <title>期末，搬宿舍时候的复杂情感</title>
    <updated>2025-06-18T17:22:46.431Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>许多想要出国留学以及热爱学习语言的人，都喜欢将自己手头上的电子设备操作系统语言设置为想学习的语言（e.g. 英语&#x2F;日语…….），但是由于Windows Font Fallback的特性，非中文语言的系统在显示中文的时候字体就会变得有些怪怪的，看着不像是平时看到的微软雅黑字体</p><p>这篇文字会详细说明非中文Windows系统下中文字体的显示问题以及如何解决（但是有些其实并不能解决）</p><h2 id="前言：更改系统语言"><a href="#前言：更改系统语言" class="headerlink" title="前言：更改系统语言"></a>前言：更改系统语言</h2><p>在讲问题之前，首先要明确要更改Windows系统语言需要哪些操作</p><p>首先，Windows专业版是需要的，现今不少电脑都只是出场安装了<strong>Windows中文单语言家庭版</strong>，是无法更改系统语言的，需要更改的话可以自行搜索“家庭版Windows升级到专业版”等教程，一般来说网上随便找个激活码再运行一个激活脚本就完成了</p><p>然后，就是更改语言的三个部分，或者说是三个等级</p><ol><li>系统设置 - 时间与语言 - 语言与地区 - Windows显示语言（Settings - Time &amp; Language - Language &amp; Region - Windows display language）</li></ol><p>这里影响了大部分Windows界面和软件的语言</p><p>如果在这里没有找到想要的语言，可以先点击下方的“添加语言”按钮添加</p><p><img src="https://s21.ax1x.com/2025/02/28/pE8Yzaq.png" alt="pE8Yzaq.png"></p><ol start="2"><li>系统设置 - 时间与语言 - 语言与地区 - 管理语言设置</li></ol><p>这里主要是影响少部分传统框架的应用，如安装器的语言等</p><p><img src="https://s21.ax1x.com/2025/03/03/pEG7y1P.md.png" alt="pEG7y1P.md.png"></p><ol start="3"><li>系统设置 - 时间与语言 - 语言与地区 - 国家和地区</li></ol><p>这里影响一些应用内的资料，如Microsoft Store的应用资料，如应用内的个性化推送等</p><h2 id="在英语-外语Windows系统下中文的问题"><a href="#在英语-外语Windows系统下中文的问题" class="headerlink" title="在英语&#x2F;外语Windows系统下中文的问题"></a>在英语&#x2F;外语Windows系统下中文的问题</h2><h3 id="Windows资源管理器问题"><a href="#Windows资源管理器问题" class="headerlink" title="Windows资源管理器问题"></a>Windows资源管理器问题</h3><p>在更改语言后，绝大部分人发现到的第一个问题都会是Windows资源管理器（Windows Explorer）文件名的字体变细和大小不一</p><p>这个问题还算好解决，只需要修改注册表中各字体的Fallback顺序即可，使用这款<a href="https://github.com/zBuffer/fix-fontlink">Fix-FontLink软件</a>就可以一件更改注册表了</p><p><img src="https://s21.ax1x.com/2025/03/03/pEGbAMV.png" alt="pEGbAMV.png"></p><h3 id="Edge问题"><a href="#Edge问题" class="headerlink" title="Edge问题"></a>Edge问题</h3><p>另一个问题就是一些地方出现的日文字体问题，如在Microsoft Edge&#x2F;Chrome浏览器的标题中，这些字体通常可以由“关复门置”这四个简体字的渲染可以看出来</p><p>不过，这其实是浏览器自己的语言系统问题，和Windows关系并不是非常大</p><p><img src="https://s21.ax1x.com/2025/03/03/pEGofKS.png" alt="pEGofKS.png"></p><p>目前来说，虽然社区中有不少人提问过，但是还是找不到什么好的方法</p><h3 id="其他问题-实用工具"><a href="#其他问题-实用工具" class="headerlink" title="其他问题&#x2F;实用工具"></a>其他问题&#x2F;实用工具</h3><p>部分程序有可能也会遇到语言乱码等问题（以日本Galgame等为主），可以尝试使用以下工具临时调整应用内的语言来解决</p><p><a href="https://github.com/xupefei/Locale-Emulator">Language Emulator</a></p>]]>
    </content>
    <id>https://haotian22.top/e0733a8a.html</id>
    <link href="https://haotian22.top/e0733a8a.html"/>
    <published>2025-02-28T00:33:57.572Z</published>
    <summary>英语/外语Windows中的中文显示问题及部分解决方法</summary>
    <title>英语/外语Windows中的中文显示问题</title>
    <updated>2025-04-08T02:29:44.959Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <category term="UIC" scheme="https://haotian22.top/tags/UIC/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>本文包含了关于 UIC VPN 与 Clash 协同使用，基本逻辑，和虚拟内网(Zerotier)&#x2F;局域网协同使用的研究</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>UIC 校园 VPN 是一个基于 Cisco Anyconnect 的 VPN ，可以在校外网络环境访问校园网资源<br>我这里使用的学校推荐的那个版本 <a href="https://itsc.uic.edu.cn/UIC%20Campus%20VPN%20Service%20Guideline.pdf">ITSC校园VPN使用指引</a></p><p>Clash 是一个基于 Go 语言的代理软件，可以实现代理转发，支持多种代理协议<br>我这里使用的是 <a href="https://github.com/LibNyanpasu/clash-nyanpasu">Clash Nyanpasu</a> 的 GUI + <a href="https://github.com/MetaCubeX/mihomo">Mihomo</a> 1.18.3 的内核<br>（但是其实用什么版本的关系并不大）</p><p>Zerotier 是一个虚拟内网软件，可以实现全球范围内的虚拟内网连接</p><p>另外，我使用的是 Windows 11 23H2 版本，用 MacOS 和 Linux 的同学需要根据这个逻辑自行调整</p><p>我研究这个的起因非常简单：我开了 UIC 校园 VPN ，但是同时又开了 Clash ，导致了<code>uic.edu.cn</code>的域名解析出现了问题，我无法访问 iSpace 交作业</p><h2 id="与Clash协同使用"><a href="#与Clash协同使用" class="headerlink" title="与Clash协同使用"></a>与Clash协同使用</h2><p>我使用的Clash，是只开了系统代理(使用 TUN 模式的话可能会有点区别)</p><p>根据7层网络模型，系统代理位于应用层（第七层），虚拟网卡工作在数据链路层（第二层）和物理层（第一层）之间，所以逻辑上流量是会先经过 Clash 再经过 VPN 的</p><p>Clash 的配置文件中，可以配置一个 <code>dns</code> 字段，用于指定 DNS 服务器</p><p>主要问题就出现在这里：Clash 的 DNS 服务器因为层级较高会被优先使用，使得<code>uic.edu.cn</code>的域名解析到了 <code>61.143.62.100</code></p><p>这个地址，是 UIC 官网在一般网络环境下访问的地址，这没有任何问题</p><p>但是，当我开了 UIC 校园 VPN 的时候，这个 IP 就会被防火墙拦截(可能是因为避免用了VPN还是使用外部地址，访问不到学术资源的”Bug”)，导致了无法访问 UIC 官网及 iSpace</p><p>那么就诞生了<strong>方法1</strong>：在 Clash 的配置文件中，将 UIC 的 <code>dns</code> 匹配改成跟随系统即可（但是还是有缺陷，看下文）</p><p>例如我写的是：</p><figure class="highlight yaml"><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"><span class="attr">dns:</span></span><br><span class="line">  <span class="string">...</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">nameserver-policy:</span></span><br><span class="line">  <span class="attr">&quot;*.uic.edu.cn&quot;:</span> [<span class="string">system</span>,<span class="string">dhcp://system</span>]</span><br><span class="line"></span><br><span class="line">  <span class="string">...</span></span><br></pre></td></tr></table></figure><p>需要注意的是，Clash-Go&#x2F;Clash-Premium&#x2F;Stash 的内核对 DNS 的配置写法有些不同，可能不支持一个地址对应多个 DNS 服务器，有需要的可以尝试以下写法</p><figure class="highlight yaml"><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="attr">dns:</span></span><br><span class="line">  <span class="string">...</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">nameserver-policy:</span></span><br><span class="line">  <span class="attr">&quot;*.uic.edu.cn&quot;:</span> <span class="string">system</span></span><br><span class="line">  <span class="string">...</span></span><br></pre></td></tr></table></figure><p>这样，Clash 就会优先使用 UIC 校园 VPN 的 DNS 服务器 (172.16.240.10)，解析出内网的 IP 地址，通过校园 VPN 访问 UIC 官网及 iSpace</p><p>然而，虽然看起来这个方案能行，但是还是有<strong>致命的缺点</strong>：DNS 缓存<br>在关闭校园 VPN 后的数分钟内，校园网连接时期的缓存依然存在，导致了一段时间内都不能访问同一个域名</p><p>为此，还有两种逻辑上可行的备用方法</p><p><strong>方法2</strong>：使用 Zero Omega 浏览器插件预先分流</p><ol><li>在Chrome商店安装插件，打开设置</li><li>从左侧进入 proxy –&gt; Bypass list，填入<code>*uic.edu.cn</code> ，再在上面的 Proxy servers 填上 Clash 本地设置的端口</li><li>点击左侧的 Apply changes</li><li>浏览器右上角的插件栏，找到这个插件，使它长期处于 proxy 模式即可</li></ol><p>这个方案，直接让浏览器预先分流，而不会受到 Clash 相关 DNS 设置的影响<br>不过缺点同样也很明显，这个方法只作用于浏览器。其他程序没法正常获取校内资源（如语言大模型(LLM)的本地客户端中，给它链接来读论文的场景就行不通了）<br>另外，对于那些经常开关 Clash 的人来说，每次关闭后都需要在浏览器右上角切换代理模式，会有点烦</p><p><strong>方法3</strong>：让 Clash 的层级比校园 VPN 的层级高（如同样用TUN）</p><p>这个方案我还没试过，但是显然会很混乱</p><p>另为了避免学术网站地址被 Clash 代理走到外国服务器，无法获取校内 IP 才能访问的资源，可以在配置文件中加入以下规则</p><figure class="highlight yaml"><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><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">rules:</span></span><br><span class="line"> <span class="string">...</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 学术论文（需要机构内网）</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,uic.edu.cn,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,acm.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,airitilibrary.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,journals.aps.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,artstor.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,gale.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,brillonline.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,corpus.byu.edu,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,zju.edu.cn,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,cambridge.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,chant.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ckcest.cn,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,cityu.edu.hk,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,cnki.net,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,corpus.byu.edu,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,csmar.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,diaolong.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,buddhism-dict.net,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,doaj.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,duxiu.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,eadl.asia,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ebscohost.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,emerald.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,myendnoteweb.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,bl.uk,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,euromonitor.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ebscohost.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,gale.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,nssd.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,global-sci.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ieee.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,imf.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,clarivate.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,jstor.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,lexisnexis.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ams.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,uic.edu.hk,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ebscohost.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,mintel.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,nlc.cn,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,nature.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,oecd-ilibrary.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,osapublishing.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,oxfordartonline.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,oxfordbibliographies.com,DIRECT</span> </span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,oxfordmusiconline.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ebscohost.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,muse.jhu.edu,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,proquest.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ebscohost.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,resset.cn,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,sagepub.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,sciencedirect.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,scival.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,scopus.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,siam.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,springer.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,tandfonline.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,tlg.uci.edu,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,warc.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,webofknowledge.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,westlaw.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,wiley.com,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,worldbank.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,zbmath.org,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ckcest.cn,DIRECT</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,cnbksy.com,DIRECT</span></span><br><span class="line"></span><br><span class="line"> <span class="string">...</span></span><br></pre></td></tr></table></figure><h2 id="VPN-逻辑的研究"><a href="#VPN-逻辑的研究" class="headerlink" title="VPN 逻辑的研究"></a>VPN 逻辑的研究</h2><p>到这里，我的问题解决了，但是我还是非常好奇，为什么会有这样一个问题出现</p><p>对此，我通过 <code>route print</code>命令，查看了一下我的路由表</p><figure class="highlight text"><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><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">IPv4 Route Table</span><br><span class="line">===========================================================================</span><br><span class="line">Active Routes:</span><br><span class="line">Network Destination        Netmask          Gateway       Interface  Metric</span><br><span class="line">          0.0.0.0          0.0.0.0      192.168.1.1    192.168.1.112     40</span><br><span class="line">          0.0.0.0          0.0.0.0   25.255.255.254   192.168.191.95  10034</span><br><span class="line">          1.0.0.0        255.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">          2.0.0.0        255.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">          3.0.0.0        255.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">          4.0.0.0        255.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">          5.0.0.0        255.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">          6.0.0.0        255.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">          7.0.0.0        255.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">          8.0.0.0        248.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">         16.0.0.0        240.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">         32.0.0.0        224.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">         64.0.0.0        192.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">     116.6.50.238  255.255.255.255      192.168.1.1    192.168.1.112     41</span><br><span class="line">  116.116.116.116  255.255.255.255      192.168.1.1    192.168.1.112     41</span><br><span class="line">        127.0.0.0        255.0.0.0         On-link         127.0.0.1    331</span><br><span class="line">        127.0.0.1  255.255.255.255         On-link         127.0.0.1    331</span><br><span class="line">  127.255.255.255  255.255.255.255         On-link         127.0.0.1    331</span><br><span class="line">        128.0.0.0        192.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">    172.16.240.10  255.255.255.255       172.25.0.1    172.25.50.204      2</span><br><span class="line">       172.25.0.0    255.255.192.0         On-link     172.25.50.204    257</span><br><span class="line">    172.25.50.204  255.255.255.255         On-link     172.25.50.204    257</span><br><span class="line">    172.25.63.255  255.255.255.255         On-link     172.25.50.204    257</span><br><span class="line">        192.0.0.0        224.0.0.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">      192.168.1.0    255.255.255.0         On-link     192.168.1.112    296</span><br><span class="line">      192.168.1.0    255.255.255.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">      192.168.1.1  255.255.255.255         On-link     192.168.1.112     41</span><br><span class="line">    192.168.1.112  255.255.255.255         On-link     192.168.1.112    296</span><br><span class="line">    192.168.1.255  255.255.255.255         On-link     192.168.1.112    296</span><br><span class="line">    192.168.191.0    255.255.255.0         On-link    192.168.191.95    291</span><br><span class="line">    192.168.191.0    255.255.255.0       172.25.0.1    172.25.50.204      2</span><br><span class="line">   192.168.191.95  255.255.255.255         On-link    192.168.191.95    291</span><br><span class="line">  192.168.191.255  255.255.255.255         On-link    192.168.191.95    291</span><br><span class="line">      221.5.88.88  255.255.255.255      192.168.1.1    192.168.1.112     41</span><br><span class="line">        224.0.0.0        240.0.0.0         On-link         127.0.0.1    331</span><br><span class="line">        224.0.0.0        240.0.0.0         On-link     192.168.1.112    296</span><br><span class="line">        224.0.0.0        240.0.0.0         On-link    192.168.191.95    291</span><br><span class="line">        224.0.0.0        240.0.0.0         On-link     172.25.50.204  10000</span><br><span class="line">  255.255.255.255  255.255.255.255         On-link         127.0.0.1    331</span><br><span class="line">  255.255.255.255  255.255.255.255         On-link     192.168.1.112    296</span><br><span class="line">  255.255.255.255  255.255.255.255         On-link    192.168.191.95    291</span><br><span class="line">  255.255.255.255  255.255.255.255         On-link     172.25.50.204  10000</span><br><span class="line">===========================================================================</span><br><span class="line">Persistent Routes:</span><br><span class="line">  None</span><br></pre></td></tr></table></figure><p>我们可以直接看出来，<code>Gateway</code>为<code>172.25.0.1</code>的便是走了 UIC 校园 VPN 的路由，而<code>Metric</code>则表达出了它的优先级，而<code>192.168.1.*</code>则是我宿舍的本地网络，<code>192.168.191.*</code>是我 Zerotier 的虚拟内网</p><p>这个路由表，并不像他看起来的那样这么好理解，我甚至还需要借助GPT来帮忙解读这个表</p><p>以下是GPT的解读结果：</p><blockquote><p>这些路由条目表示特定的IPv4地址范围是通过VPN隧道传输的。具体的路由如下：<br>128.0.0.0&#x2F;2 覆盖128.0.0.0到191.255.255.255的IP地址范围。<br>192.0.0.0&#x2F;3 覆盖192.0.0.0到223.255.255.255的IP地址范围。<br>64.0.0.0&#x2F;2 覆盖64.0.0.0到127.255.255.255的IP地址范围。<br>32.0.0.0&#x2F;3 覆盖32.0.0.0到63.255.255.255的IP地址范围。<br>16.0.0.0&#x2F;4 覆盖16.0.0.0到31.255.255.255的IP地址范围。<br>8.0.0.0&#x2F;5 覆盖8.0.0.0到15.255.255.255的IP地址范围。<br>1.0.0.0&#x2F;8 覆盖1.0.0.0到1.255.255.255的IP地址范围。<br>2.0.0.0&#x2F;8 覆盖2.0.0.0到2.255.255.255的IP地址范围。<br>3.0.0.0&#x2F;8 覆盖3.0.0.0到3.255.255.255的IP地址范围。<br>4.0.0.0&#x2F;8 覆盖4.0.0.0到4.255.255.255的IP地址范围。<br>5.0.0.0&#x2F;8 覆盖5.0.0.0到5.255.255.255的IP地址范围。<br>6.0.0.0&#x2F;8 覆盖6.0.0.0到6.255.255.255的IP地址范围。<br>7.0.0.0&#x2F;8 覆盖7.0.0.0到7.255.255.255的IP地址范围。<br>172.16.240.10&#x2F;32 表示单个IP地址172.16.240.10通过VPN隧道传输。<br>总结来说，这段配置定义了哪些流量应通过VPN（安全路由）以及哪些流量应通过本地网络（非安全路由）。在这个配置中，除了0.0.0.0&#x2F;0表示的默认所有流量之外，其他一些特定的地址段将会通过VPN进行传输。</p></blockquote><p>那么，我们就可以发现，UIC 的外部 IP 地址 <code>61.143.62.100</code>，是需要通过 VPN 隧道传输，并不能像我们想的那样可以<strong>按需传输</strong>，给了位于校园网内部的防火墙可以拦截的机会</p><p>所以，这也带来了一个问题：在开启 VPN 之后，因为 VPN 的路由表优先级高于本地网络，所以会导致一些本地网络的问题<br>比如无法访问局域网内的设备等</p><p>但是，我又发现，我还可以访问 <code>192.168.1.1</code>，它明明是优先级41的，比2高了许多，为什么还可以访问呢？</p><p>这又得提到另外一个机制了，因为我也不太懂，所以GPT是这么解释的</p><blockquote><p>精确匹配原则:<br>路由表的查找遵循“最长前缀匹配”原则，即最精确匹配的路由条目优先。例如，192.168.1.1 255.255.255.255 的匹配精度高于 192.168.1.0&#x2F;24。<br>因此，当你访问本地地址192.168.1.1时，路由表选择最精确的匹配，即 192.168.1.1 255.255.255.255 On-link 192.168.1.112，而不是通过网关 172.25.0.1。这是为什么你仍然可以访问本地的192.168.1.1，即使有一个低metric值的路由条目存在。  </p></blockquote><h2 id="与虚拟内网-Zerotier-局域网协同使用"><a href="#与虚拟内网-Zerotier-局域网协同使用" class="headerlink" title="与虚拟内网(Zerotier)&#x2F;局域网协同使用"></a>与虚拟内网(Zerotier)&#x2F;局域网协同使用</h2><p>研究完了 VPN 的逻辑，逻辑能力强的人就会发现一个问题：既然VPN的路由表优先级高于本地网络，那么本地内网的设备是不是会优先被路由去了学校里面，导致内网的设备无法访问吗？</p><p>是的，实际操作确实会出现无法访问本地网络设备的情况</p><p>理论上，我们可以通过修改路由表来解决这个问题，然而，我每次修改完貌似都不起作用<br>我尝试过输入<code>route delete 192.168.191.0 172.25.0.1</code>，但是貌似并没有什么用<br>经过一段时间尝试修改路由表，但是依然不生效，有可能是因为 Cisco Anyconnect 会不断覆盖我的路由表，导致了我无法修改，所以放弃了这个方案</p><p>但是，我偶尔又想到了另一条路子：为什么我非要使用 Cisco Anyconnect Client 来连校园 VPN 呢？</p><p>所以，我找到了另一个支持 Anyconnect 协议的开源平替：<a href="https://www.infradead.org/openconnect/">OpenConnect</a><br>单看这个界面，非常的老旧，还都是命令行，非常的难用，所以我又找到基于它的GUI版本：<a href="https://gui.openconnect-vpn.net/">OpenConnect-GUI</a></p><p>这有GUI就很简单啦，使用方法：</p><ol><li>下载好软件，安装</li><li>右上角 File -&gt; Profile -&gt; New Profile</li><li>把<code>v.uic.edu.cn</code>填入<code>Gateway</code>，点<code>Save</code>即可</li></ol><p>下次连接的时候，只需要点<code>Connect</code>，<code>Group</code>选<code>Student</code>，输入自己的账号密码和验证码即可</p><p>这下，我就可以通过 OpenConnect-GUI 来连接校园VPN，而不是 Cisco Anyconnect Client<br>并且，这个软件配置出来的路由表更加合理，不需要经过任何修改就可以实现局域网&#x2F;虚拟内网和校园网的协同使用</p><p>这是我在 OpenConnect-GUI 连接校园 VPN 之后的路由表</p><figure class="highlight text"><figcaption><span>route print</span></figcaption><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><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">IPv4 Route Table</span><br><span class="line">===========================================================================</span><br><span class="line">Active Routes:</span><br><span class="line">Network Destination        Netmask          Gateway       Interface  Metric</span><br><span class="line">          0.0.0.0          0.0.0.0   25.255.255.254   192.168.191.95  10034</span><br><span class="line">          0.0.0.0          0.0.0.0      192.168.1.1    192.168.1.112     40</span><br><span class="line">          1.0.0.0        255.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">          2.0.0.0        255.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">          3.0.0.0        255.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">          4.0.0.0        255.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">          5.0.0.0        255.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">          6.0.0.0        255.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">          7.0.0.0        255.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">          8.0.0.0        248.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">         16.0.0.0        240.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">         32.0.0.0        224.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">         64.0.0.0        192.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">     116.6.50.238  255.255.255.255   25.255.255.254    192.168.1.112     41</span><br><span class="line">        127.0.0.0        255.0.0.0         On-link         127.0.0.1    331</span><br><span class="line">        127.0.0.1  255.255.255.255         On-link         127.0.0.1    331</span><br><span class="line">  127.255.255.255  255.255.255.255         On-link         127.0.0.1    331</span><br><span class="line">        128.0.0.0        192.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">       172.25.0.0    255.255.192.0         On-link     172.25.50.214    261</span><br><span class="line">    172.25.50.214  255.255.255.255         On-link     172.25.50.214    261</span><br><span class="line">    172.25.63.255  255.255.255.255         On-link     172.25.50.214    261</span><br><span class="line">        192.0.0.0        224.0.0.0    172.25.50.214    172.25.50.214      6</span><br><span class="line">      192.168.1.0    255.255.255.0         On-link     192.168.1.112    296</span><br><span class="line">    192.168.1.106  255.255.255.255      192.168.1.1    192.168.1.112     41</span><br><span class="line">    192.168.1.107  255.255.255.255      192.168.1.1    192.168.1.112     41</span><br><span class="line">    192.168.1.112  255.255.255.255         On-link     192.168.1.112    296</span><br><span class="line">    192.168.1.255  255.255.255.255         On-link     192.168.1.112    296</span><br><span class="line">     192.168.43.0    255.255.255.0   192.168.191.32   192.168.191.95    291</span><br><span class="line">    192.168.191.0    255.255.255.0         On-link    192.168.191.95    291</span><br><span class="line">   192.168.191.95  255.255.255.255         On-link    192.168.191.95    291</span><br><span class="line">  192.168.191.255  255.255.255.255         On-link    192.168.191.95    291</span><br><span class="line">        224.0.0.0        240.0.0.0         On-link         127.0.0.1    331</span><br><span class="line">        224.0.0.0        240.0.0.0         On-link    192.168.191.95    291</span><br><span class="line">        224.0.0.0        240.0.0.0         On-link     192.168.1.112    296</span><br><span class="line">        224.0.0.0        240.0.0.0         On-link     172.25.50.214    261</span><br><span class="line">  255.255.255.255  255.255.255.255         On-link         127.0.0.1    331</span><br><span class="line">  255.255.255.255  255.255.255.255         On-link    192.168.191.95    291</span><br><span class="line">  255.255.255.255  255.255.255.255         On-link     192.168.1.112    296</span><br><span class="line">  255.255.255.255  255.255.255.255         On-link     172.25.50.214    261</span><br><span class="line">===========================================================================</span><br><span class="line">Persistent Routes:</span><br><span class="line">  None</span><br></pre></td></tr></table></figure><p>显然合理多了，根据前面提到的“精确匹配原则”，本地的设备就可以正确匹配到本地的小局域网里，不会被大包围路由到学校 VPN 隧道里了</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>这篇文章，记录了我对 UIC 校园 VPN 的研究，以及与 Clash 和虚拟内网的协同使用</p><p>但是我并非专业人士，甚至都不是 FST 的学生，大部分内容都是从网上细碎的片段配合 GPT4o 的解释来理解的，所以文章中可能有错误，欢迎在评论区指正</p>]]>
    </content>
    <id>https://haotian22.top/546c6c89.html</id>
    <link href="https://haotian22.top/546c6c89.html"/>
    <published>2024-05-22T17:01:07.000Z</published>
    <summary>本文包含了关于 UIC 的 Anyconnect VPN 与 Clash 协同使用，校园VPN基本逻辑，和虚拟内网(Zerotier)/局域网协同使用的研究</summary>
    <title>UIC Anyconnect的校园VPN研究</title>
    <updated>2024-05-22T23:53:33.836Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>现在，随着个人智能设备越来越多，各种设备之间的协同操作也变得越来越重要<br>细化到远程控制这一类，其实也有很多的需求，如：</p><ol><li>在外控制电脑办公</li><li>把移动设备在家当成电脑扩展屏</li><li>使用Pad用作电脑的画板</li></ol><p>这篇文章就以iPad和Windows 11的Laptop为例，重点以以上三个方面对比分析以下几款PC远程控制软件的使用体验</p><p>本次第一轮参与选手分别为：RDP(Microsoft)，Spacedesk，Moonlight(Sunshine)<br>（如果以后有机会的话可能会更新更多的参赛选手）</p><span id="more"></span><h2 id="RDP"><a href="#RDP" class="headerlink" title="RDP"></a>RDP</h2><p>纯粹的远程控制软件</p><p>RDP是微软自家的远程桌面协议，对于Windows来说兼容性比较好，而且Windows也自带了RDP服务，所以使用起来比较方便  </p><p>对触摸支持好，支持Apple Pencil压感，延迟还算可以<br><img src="https://s21.ax1x.com/2024/05/19/pkuqKbT.png" alt="Screenshot"><br>性能开销较小，看视频比较流畅，还可以调用移动设备的摄像头和麦克风，以及访问共享文件夹  </p><p>但是不能把它当扩展屏软件来使用  </p><h2 id="Spacedesk"><a href="#Spacedesk" class="headerlink" title="Spacedesk"></a>Spacedesk</h2><p>全面但低效的扩展屏软件<br>有多设备的支持，有原生触摸支持（但是相对应的不支持鼠标），支持Apple Pencil压感，可以通过直接连USB线来减小延迟，可以说是功能非常优秀的软件了<br><img src="https://s21.ax1x.com/2024/05/19/pkuqevq.png" alt="Screenshot"></p><p>在使用USB Cable连接的情况下，平时延迟在100ms左右，但是画面变动幅度大的时候延迟会上升到200ms<br>我估计是因为用了压缩算法导致的延迟不平均</p><p>但是性能很烂，一打开就占用20%的GPU，画面变动幅度大的时候更是能干到25%GPU和20%CPU，同时也因为编解码效率过低导致了延迟很高，同时，也因此导致两边都性能开销非常大，有些时候重度用下来几十分钟电脑和Pad都是烫的</p><h2 id="Moonlight"><a href="#Moonlight" class="headerlink" title="Moonlight"></a>Moonlight</h2><p>偏向于游戏的优化，虽然不能连接USB，但是纯网络的延迟也不是非常高</p><p>不支持原生触摸，导致操作其实很别扭（但是支持鼠标），支持Apple Pencil压感<br>不过在书写的时候，不知道因为什么原因，笔迹经常会传不过去断开，导致书写体验很差<br><img src="https://s21.ax1x.com/2024/05/19/pkuq8PJ.png" alt="Screenshot"><br>后面发现可以把iPad客户端设置为TouchPad模式解决笔迹断开的问题<br>性能开销主要在电脑上，另外因为没有压缩，其实GPU的消耗并不大，5%左右，CPU占30%左右，但是因为不用交给DWM处理（相比于Spacedesk来说），其实总体占用比Spacedesk低点<br>可以配置为不压缩，这样电脑占用基本没有了，延迟也是还算可以的水平</p><hr><p>2026年更新：</p><p>Moonlight 发展出了很多的第三方客户端，也变得好用起来了</p><p><a href="https://github.com/qiin2333/foundation-sunshine">Moonlight-foundation</a> 可以支持模拟虚拟屏幕，做到物理屏息屏远控和扩展屏的效果</p><p>iOS&#x2F;iPadOS 方面，<a href="https://github.com/The-Fried-Fish/VoidLink-previously-moonlight-zwm">Voidlink</a> 补全了原版 Moonlight 的许多缺陷，修复了 Apple Pencil 的笔迹输入的问题，支持了触控输入，并且在性能和易用性上也有了很大的提升，延迟也降低了很多</p><p>另外，如果需要出门在外连接电脑的话，需要自己搭建虚拟网环境，延迟直接取决于搭建的网络环境<br>我目前使用 Tailscale + Aliyun 中国内地 Peer-relay 中转的的方案，延迟大概在 40ms 左右，但是因为 Aliyun 99一年的 VPS 带宽只给到了 3Mbps，看视频&#x2F;玩游戏的时候画面会糊成一团</p><h2 id="网易UU远程"><a href="#网易UU远程" class="headerlink" title="网易UU远程"></a>网易UU远程</h2><p>这个应该是对于新手来说最好的软件了，不用复杂的配置，登录直接就能用，目前来说控制自己的电脑也是免费的</p><p>功能上，它也支持扩展屏和息屏串流的模式，支持触控输入（但是用起来没 Moonlight 第三方的版本那么好）<br>延迟方面，使用官方的免费中转延迟大概在 80ms 左右，但是码率给的挺高的，可以去到 10Mbps，跑 2k 的 iPad 屏幕基本什么没有什么问题</p>]]>
    </content>
    <id>https://haotian22.top/82a74aad.html</id>
    <link href="https://haotian22.top/82a74aad.html"/>
    <published>2024-05-19T07:17:00.000Z</published>
    <summary>
      <![CDATA[<p>现在，随着个人智能设备越来越多，各种设备之间的协同操作也变得越来越重要<br>细化到远程控制这一类，其实也有很多的需求，如：</p>
<ol>
<li>在外控制电脑办公</li>
<li>把移动设备在家当成电脑扩展屏</li>
<li>使用Pad用作电脑的画板</li>
</ol>
<p>这篇文章就以iPad和Windows 11的Laptop为例，重点以以上三个方面对比分析以下几款PC远程控制软件的使用体验</p>
<p>本次第一轮参与选手分别为：RDP(Microsoft)，Spacedesk，Moonlight(Sunshine)<br>（如果以后有机会的话可能会更新更多的参赛选手）</p>]]>
    </summary>
    <title>PC远程控制软件小横评</title>
    <updated>2026-04-12T07:17:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>不知道为什么，我的Windows电脑经常用着用着，就断网了，具体表现为任务栏现实”No internet access”，实际上Ping内网设备都不通<br>很显然，连内网都有问题的，大概率是电脑&#x2F;Windows驱动的问题，而不是路由器的问题<br>而这个问题，其实也很好解决，把WiFi断开再连接一下就好了</p><p>然而，在我对于远程桌面依赖比较高的情况下，这个问题就比较棘手了——毕竟，断网了，远程桌面也就断了，我就得跑到电脑前去解决这个问题，严重影响了我的工作效率</p><h2 id="网络结果综述"><a href="#网络结果综述" class="headerlink" title="网络结果综述"></a>网络结果综述</h2><p>有人说，可能是电脑的<a href="https://www.zhihu.com/question/27914005/answer/2470199887">WiFi适配器驱动有问题</a>，需要换一个驱动</p><p>有人说，可能是<a href="https://www.zhihu.com/question/28422159">USB和WiFi产生干扰</a>，需要把USB设备和WiFi模块隔开比较远的距离</p><p>还有的人在捣鼓注册表，我根本都看不懂</p><p>这些方案中，有可能是有真正的解决问题的，但是断网这个问题的触发条件太过随机，我也不知道哪个方案是真正有效的，验证起来也毕竟花费时间</p><p>所以，我想到了一个简单的办法——写一个脚本，检测到断网的时候，自动断开WiFi再连接一下，先将就着用着，靠后面脚本的log再看看是什么原因导致的断网</p><h2 id="脚本编写"><a href="#脚本编写" class="headerlink" title="脚本编写"></a>脚本编写</h2><p>说实话，我对写bat真的一窍不通，所以以下部分都是把我的思路表述出来丢给LLM去写的，可能比较粗糙，但是能用就行</p><p><a href="https://github.com/HaoTian22/UIC-Utilities/blob/main/AutoRestartWIFI.bat">脚本在Github上的链接</a></p><p>如果还不懂怎么用这个脚本，可以看该仓库下的README</p>]]>
    </content>
    <id>https://haotian22.top/4e4c225c.html</id>
    <link href="https://haotian22.top/4e4c225c.html"/>
    <published>2024-05-18T18:07:18.000Z</published>
    <summary>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla]]>
    </summary>
    <title>Windows断网自动重连脚本</title>
    <updated>2024-05-18T18:07:18.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="UIC" scheme="https://haotian22.top/tags/UIC/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>你是否有这样的困扰：学期结束了，但是课程内容还没有完全掌握，想以后从事习惯内容再看的时候找不到资料？</p><p>你是否有这样的烦恼：iSpace上的课程资料太多，想要下载的时候一个一个下载太麻烦？</p><p>你是否有这样的需求：想要将iSpace上的课程资料归档到自己的电脑上，方便查阅？</p><p>如果你有以上的困扰、烦恼和需求，那么这篇文章就是为你准备的！</p><p>本文将介绍两个方便快捷的Chrome插件，帮助你一键归档iSpace上的课程资料，让你的学习更加高效</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>iSpace是基于Moodle搭建的学习和课程管理平台,而Moodle,又是一个被广泛使用的开源软件,有不少人为此编写了各种插件</p><p>因此,大部分浏览器上适用于Moodle的插件也适用于iSpace</p><h2 id="软件介绍"><a href="#软件介绍" class="headerlink" title="软件介绍"></a>软件介绍</h2><h3 id="Moodle-Downloader"><a href="#Moodle-Downloader" class="headerlink" title="Moodle Downloader"></a>Moodle Downloader</h3><p>Moodle Downloader是一款Chrome插件,可以在<a href="https://chromewebstore.google.com/detail/moodle-downloader/egdkcpdcopinmhfphdmdagnpogclbpln">Chrome网上应用商店</a>下载</p><p>它的优点有:</p><ul><li>界面简洁,操作简单,上手容易</li><li>下载速度快</li></ul><h3 id="Moodle-Buddy"><a href="#Moodle-Buddy" class="headerlink" title="Moodle Buddy"></a>Moodle Buddy</h3><p>Moodle Buddy是一款Chrome插件,可以在<a href="https://chromewebstore.google.com/detail/moodle-buddy/nomahjpllnbcpbggnpiehiecfbjmcaeo">Chrome网上应用商店</a>下载</p><p>它的优点有:</p><ul><li>功能详细,可以挑选下载的文件,也可以自定义重命名</li></ul><p>但是也有缺点:</p><ul><li>下载某些视频会失败</li></ul><h3 id="Moodle-dl"><a href="#Moodle-dl" class="headerlink" title="Moodle-dl"></a>Moodle-dl</h3><p>Moodle-dl是一款Python脚本,可以在<a href="https://github.com/C0D3D3V/Moodle-DL">GitHub</a>按照上面的说明使用</p><p>但是这是一款命令行程序,不熟悉Python和命令行的同学可能不太容易上手</p><p>优点有:</p><ul><li>可以下载所有文件,包括视频(但是下载Quiz还是有点问题)</li><li>可以选择下载的文件类型(但是不能选择具体文件)</li><li>可以自定义课程重命名(文件夹重命名)</li></ul>]]>
    </content>
    <id>https://haotian22.top/f08b4725.html</id>
    <link href="https://haotian22.top/f08b4725.html"/>
    <published>2024-04-30T13:04:52.000Z</published>
    <summary>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla]]>
    </summary>
    <title>学期结束但是课程内容没完全掌握？来一键归档iSpace！</title>
    <updated>2024-04-30T13:04:52.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="生活" scheme="https://haotian22.top/tags/%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>事情的起因是这样的……</p><p>近几天和同学去了一次澳门旅游，但是当我们去到大三巴牌坊附近的时候，因为小巷子比较多不会走，打开 Google Map 一看，整个地图就像是被炮弹炸过一样，变得零零碎碎，属于完全不可用的状态</p><img src="https://s11.ax1x.com/2024/03/04/pFDSrcR.jpg" style="width:20%;" alt="破碎地图截图"/><p>而且受到影响的位置不仅仅是大三巴附近，整个澳门半岛的西部都处于渲染错误的状态（但是导航和公交却是基本可用的，会在空白处描出一条线）</p><img src="https://s11.ax1x.com/2024/03/04/pFDpjZ6.jpg" style="width:20%;" alt="破碎地图完整截图"/><p>我们没有办法，只能关掉Google Map，使用其他地图软件来查看。</p><p>后面从澳门回来后，我越想越奇怪：明明我元旦去澳门的时候的地图是正常的啊，为什么这次会变得这么奇怪呢？</p><p>于是，我又把自己的遭遇和其他的网友探讨了一番，得出了一个初步的结论：Google Map 会根据不同地区渲染不同的地图</p><p>经过多次测试，我发现，Google Map 在 Android 手机上，第一个判断地区的方法是看SIM卡</p><p>我尝试了大陆单卡、香港单卡、双卡（大陆在SIM1，香港在SIM2）的情况，结果发现，无论使用什么网络，在大陆单卡或双卡的情况下（即使是开了飞行模式让该卡无信号），地图都会出现破碎的情况；而在使用香港卡的情况下，则会出现正常的地图</p><img src="https://s11.ax1x.com/2024/03/04/pFDpxIO.jpg" style="width:20%;" alt="正常地图截图"/><p>我们再进一步探究，其本质是两个地图之间渲染优先度和地图坐标系的问题</p><p>众所周知，因为一些<del>我也不知道他们在干什么的</del>原因，中国大陆公司制作的地图所使用的都是和国际惯例不同的坐标系，这就导致了地图不同程度的偏移</p><p>而 Google Map 在大陆使用的地图是向大陆公司购买的，遵循了大陆的特殊坐标系，导致了部分地区偏移到了澳门半岛内</p><p>又因为渲染优先性的原因，在使用大陆 SIM 卡时，会优先确保大陆地图的完整性，这使得部分澳门区域被大陆偏移的地图重叠覆盖</p><p>此外，我还发现，<strong>Google Map 的评论系统</strong>在上面提到的“非大陆 SIM 卡”情况下才能正常工作，否则则会弹出无法连接到地图的提示</p><p>而在无卡的情况下，有人推测，也可能会参考网络的IP地址和设备的区域设置，来决定是否使用的地图，但是我没有足够的经验去探究这个问题（<del>懒</del>）</p><p>所以，很显然，最快最简单的解决方法就是<strong>关闭&#x2F;移除大陆的 SIM 卡</strong>，这样就可以正常使用 Google Map 了</p><p>2025.1.5补充：除此之外，懂玩机的用户还可以试试这个使用 Shizuku 的<a href="https://github.com/Ackites/Nrfr">SIM 卡国家码修改工具</a>来改改SIM卡区域使得插上大陆卡正常工作，经过实际测试发现，双卡用户只需要把 SIM1 改成非大陆地区（我改的是香港），关闭 Google Map 后台再打开就可以正常工作了</p><p>Credits:<br>Cover - Generated by Paint Cocreator</p>]]>
    </content>
    <id>https://haotian22.top/78681fb.html</id>
    <link href="https://haotian22.top/78681fb.html"/>
    <published>2024-03-04T11:30:07.000Z</published>
    <summary>这篇博客文章探讨了 Google 地图在澳门使用时出现的地图渲染问题。作者发现，在澳门半岛西部，特别是大三巴牌坊附近，Google 地图会出现碎片化、不可用的情况。经过测试，作者得出结论：**Google 地图会根据 SIM 卡地区进行不同的地图渲染。当使用中国大陆 SIM 卡时，即使使用其他网络，地图也会出现错误；而使用香港 SIM 卡时，地图则显示正常。**  文章进一步解释说，这是因为中国大陆使用的地图坐标系与国际标准不同，导致 Google 地图在大陆使用的地图发生偏移，并覆盖了部分澳门区域。同时，Google 地图会优先保证大陆地图的完整性，加剧了澳门地图的渲染问题。文章还指出，评论功能也受到 SIM 卡地区的影响。作者建议，**最简单的方法是移除或关闭大陆 SIM 卡以正常使用 Google 地图**，或者可以通过修改 SIM 卡国家码的方式来解决问题，这对于双卡用户尤为适用。</summary>
    <title>Google Map澳门半岛渲染出错之探究</title>
    <updated>2025-01-05T10:05:32.673Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="电脑" scheme="https://haotian22.top/tags/%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>在中秋+国庆的长假中，由于未知原因，在2023.9.29起，使用 Vercel 搭建的网站遭到了中国移动的屏蔽（抢发 Reset），到了2023.10.1，更是遭到了中国移动、联通和少部分地区的电信的阻断</p><div align=center><img src="https://z1.ax1x.com/2023/10/03/pPLOtbV.png" style="zoom:5%;" /></div><p>我目前还只是一个学生，具体上面为什么要怎么做，我并不清楚，对于这种现象我也是无能为力<br>国内服务器的话，由于成本等原因，也暂时不考虑（现在这个站近乎开销为0）</p><p>就这样，我在2023.10.3看起来并没有将我站解封之后，运用了一下 Cloudflare 的反代，使得其勉强能够在中国大陆正常访问，但是同样的，经过了反代连接速度有可能会下降，请见谅</p><p><img src="https://z1.ax1x.com/2023/10/03/pPLOaUU.png" alt="CF反代.png"></p><p>至于以后怎么办，只能说能处理我就尽量处理，实在没办法的只能放弃掉中国大陆的访客，让他们自己想办法上来吧</p><p>给了我友联的朋友们发现我网站访问不了了也别以为我关站了，可能只是运营商在搞鬼而已，别把我删了啊</p>]]>
    </content>
    <id>https://haotian22.top/252d8e01.html</id>
    <link href="https://haotian22.top/252d8e01.html"/>
    <published>2023-10-02T18:33:52.316Z</published>
    <summary>Vercel 搭建的网站在 2023.10 期间遭到了中国大陆运营商的部分屏蔽，由于成本问题可能导致一些地方读者无法访问该网站</summary>
    <title>对于有限度维持中国大陆部分网络环境可访问性的说明</title>
    <updated>2023-10-02T18:33:52.316Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="生活" scheme="https://haotian22.top/tags/%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>近年来，随着 ChatGPT 等 LLM 的流行，各种较为简单的工作都可以交给他们，提高了我们的工作效率，在这里，我想分享一种 LLM 的妙用技巧：生成 ics 协助日程管理</p><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>这个想法的起源可以追溯到几个星期前，几个星期前，我刚刚进入大学，不得不说，大学的生活和高中的完全不一样，这里没有了班主任去督促我该什么时间做什么，也没有了同班同学可以供我询问。</p><p>因此，只要信息量一大，我就经常容易忘掉事情，甚至将2个同一时间的活动都接了下来导致到时候会非常尴尬。</p><p>所以我就开始用手机上的日历来做日程管理，但是其实这样的方法并不是特别的好，我还要花起码1~2分钟的时间去编写日历，不小心看走眼还容易会写错时间和地点。就这样，我把目光转向了 ChatGPT。</p><p>用了 ChatGPT 之后，它确实能帮我总结和提取重要的信息点，但是新的问题又来了，我如何把它导入到手机日历里面呢？</p><p>我时候就在网上搜索了一会儿，找到了一个叫 <a href="https://icalendar.org/Home.html">iCalendar</a> 的标准，它可以兼容大部分设备，并且我也在 ChatGPT 里面试了一下，可以正常的生成，虽然过程中还是可能出现一些小错误导致时间不对等等，但是后面发现都是因为我的prompt不够详细的导致的。</p><p>因此，我改了又改，试了又试，做出了这篇文章的 prompt</p><h2 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h2><p>prompt 如下</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><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">在接下来的对话中，我希望你可以根据我的聊天记录生成符合iCalendar File Format RFC 7986的文本，让我复制导入到outlook邮箱中，时区默认为上海，UID默认为10位数的随机字符串，默认设定提前一个小时提醒，其他内容的标准模板如下</span><br><span class="line">BEGIN:VCALENDAR</span><br><span class="line">VERSION:2.0</span><br><span class="line">PRODID:-//ZContent.net//Zap Calendar 1.0//EN</span><br><span class="line">METHOD:PUBLISH</span><br><span class="line">BEGIN:VEVENT</span><br><span class="line">SUMMARY:Abraham Lincoln</span><br><span class="line">UID:c7614cff-3549-4a00-9152-d25cc1fe077d</span><br><span class="line">STATUS:CONFIRMED</span><br><span class="line">DTSTART:20080212</span><br><span class="line">DTEND:20080213</span><br><span class="line">DTSTAMP:20150421T141403</span><br><span class="line">LOCATION:Hodgenville\, Kentucky</span><br><span class="line">DESCRIPTION:Born February 12\, 1809\nSixteenth President (1861-1865)\n\n\n</span><br><span class="line"> \nhttp://AmericanHistoryCalendar.com</span><br><span class="line">END:VEVENT</span><br><span class="line">END:VCALENDAR</span><br><span class="line">仅需生成需要导入的内容，无需生成其他附加说明</span><br></pre></td></tr></table></figure><p>将这个 prompt 输入 ChatGPT，再将各个信息渠道的日程安排全部丢进去，就会自动输出一个可以复制的文本，这时需要新建一个后缀为 ics 的文件，打开就可以直接导入了</p><p>值得注意的是，我这个 prompt 的时区被默认设置为上海。如果你不是在中国的话，你需要自己更改自己时区所在的城市，不然，你的时间设置就会混乱。</p><p>此外，有时候 ChatGPT 不能正确输出 UID 字段，UID 重复会导致先前同一个 UID 的日程被覆盖。因此，在复制之前，你还需要检查一下这个 UID 是不是非常眼熟，以确保 UID 的唯一性</p><p>当然，我的这个方法还有很多可以改进的地方，例如我现在需要手动把聊天记录导入 ChatGPT 才能使用，生成内容后，我也还得自己复制和粘贴。如果各位有什么更好的方法也可以在评论区提一下（尽量可以不用依赖 OpenAI API），希望能将这个方法再改进的更加方便一点吧。</p>]]>
    </content>
    <id>https://haotian22.top/759a00b.html</id>
    <link href="https://haotian22.top/759a00b.html"/>
    <published>2023-09-30T11:13:59.233Z</published>
    <summary>使用 AI 生成 ics 文件来管理日程的一种 ChatGPT 妙用</summary>
    <title>将 AI 和 ics 文件结合的日程管理方式</title>
    <updated>2023-09-30T11:13:59.233Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="随笔" scheme="https://haotian22.top/tags/%E9%9A%8F%E7%AC%94/"/>
    <category term="思考" scheme="https://haotian22.top/tags/%E6%80%9D%E8%80%83/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前言：我与博客的经历"><a href="#前言：我与博客的经历" class="headerlink" title="前言：我与博客的经历"></a>前言：我与博客的经历</h2><h3 id="加入博客"><a href="#加入博客" class="headerlink" title="加入博客"></a>加入博客</h3><p>我加入博客的时候正值初三网课，因为看见 Minecraft 游戏群里面大多数人都有一个自己的博客，我闲着无聊，加上网上教程比较完善，便花了两天基本上搞出了一个博客。建博客的初心也非常简单，就是好玩。</p><h3 id="发展期-or-折腾期"><a href="#发展期-or-折腾期" class="headerlink" title="发展期 or 折腾期"></a>发展期 or 折腾期</h3><p>在搭建博客以后，这里就成为了我新手乱改 CSS 的训练场，我将原本的 yilia 主题大动干戈地修改，改了布局，改了界面，改了风格，但是又因为我没有理解原本主题的设计思路，只是按照自己的想法上手改，便改出了许多 bug，元素错位，JS 调用报错等都是常有的事情。但是，基本上我每次来折腾博客都能够玩的不亦乐乎，甚至玩到熬夜2点才睡。</p><h3 id="平静期-or-瓶颈期"><a href="#平静期-or-瓶颈期" class="headerlink" title="平静期 or 瓶颈期"></a>平静期 or 瓶颈期</h3><p>上高中后，因为上的是全宿舍的学校，我平时能用的就只有一台手机，也就只能在后台随便写写文章，基本上没有什么过多的精力去改动页面和其他内容。</p><p>不过，即使有些技术类文章虽然自认为写得不怎么样，但是浏览量却非常高，而一些生活类文章想了很久，却没多少人看</p><p>我也曾经想过要把部分日记脱敏后公开发上来，但是在尝试了几次，发现文章风格和全站的风格都不太搭，后面就没再继续了（可能以后会重启？）</p><p>虽然多的文章也就几百个阅读量，并不能实际给我带来什么利益，我当初也并不是因为想要流量而去建站的，但是这样的现象不禁让我思考：这个博客的到底是为了谁而服务？</p><h2 id="为什么我要写博客"><a href="#为什么我要写博客" class="headerlink" title="为什么我要写博客"></a>为什么我要写博客</h2><p>我可能和很多人的习惯都有些不同，从小学的时候开始，我就基本上有定期写日记的习惯，到2019年，我还把纸质日记变成了电子日记，更方便以后的查阅和储存。因此在我看来，在我博客上写记录生活类的文章，其充分性是有些不足的。毕竟在自己的日记里，就算胡说八道都没有人知道，更不用担心泄露隐私的问题，显然需要比在这里写更加自由的。况且除了一些真正关心我的朋友，几乎没有人会花费大量的时间去阅读我之前的经历……</p><p>那么，是服务他人吗？我觉得是又不完全是。显然现在网络上的博客，大多数都是以技术分享偏多，这类群体显然使用搜索引擎比较多的，遇到的问题可能也比较分散，所以博客能够被搜索到，而且有一定的浏览量，从这方面来说，建一个博客是非常合适的。</p><p>但是我也同时在思考，博客，还有没有其他的出路。在我看来生活分享类的文章，主要集中在知乎和小红书等平台，浏览的方式也大多数也是通过内容平台的算法推送，而不是用户主动发起的搜索。在这种状况下，内容分散的博客就比较难访问到，失去了它的优势。</p><p>当然，我希望能把我日记中的一部份内容搬到我的博客上，听听不同人的分析，听听不同人的看法，但是又担心会不会稀释掉首页进来的技术宅，使得整个博客风格都有点不伦不类</p><p><strong>那么……我写博客的意义何在？</strong></p><p>综合上面的思考，我认为，博客是突破信息茧房的一种方式，是对大数据和算法无声的对抗。在每个平台都用大数据了解用户习惯的2023年，读博客可以了解更多方面的知识，不被算法和权威左右。比如那些网络热点，可以提供一些主流媒体没有报道的角度，又比如一些放在内容平台容易被封号限流的内容，例如关于某些争议事件背后的法律研究。但是可惜的是，很多优质内容依然藏在搜索引擎之后，想要访问到他们还是有一定的难度。</p><p>博客，也是一种交朋友的方式。尽管在2023年，写博客的人相对于其他社交媒体的人数占比会越来越少，但是这也间接筛选了社交圈的品质。到如今还在写博客的人，至少可以表明他们有一定的耐心和恒心，也对自己和周围人的生活有一定的思考。</p><p>博客，也能让我培养学科总结的思维。我想，博客和日记最大的不同之处就在于日记几乎不会出现和学习相关的思维导图、对学科的见解等内容（除了语文和哲学？），虽然我现在还没开始这样做，但是我也了解到有不少让写博客都会在博客上总结自己的上课内容。在将来，我可能也会开始这样的尝试。</p>]]>
    </content>
    <id>https://haotian22.top/1333aac3.html</id>
    <link href="https://haotian22.top/1333aac3.html"/>
    <published>2023-09-27T06:48:45.680Z</published>
    <summary>对我写博客的意义和本博客将来的规划的一些思考</summary>
    <title>反思：为什么要写博客</title>
    <updated>2023-09-27T06:48:45.680Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <category term="随笔" scheme="https://haotian22.top/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>Windows 11，作为微软新一代的操作系统，于2021年6月发布</p><p>作为一个喜欢体验新鲜事物的人，在同年的8月，便用我已经比较年迈的华硕 UX31E 通过替换文件强行升级体验到了这最新的操作系统 Windows 11 22000</p><p>而时隔2年的今天，因为这华硕 UX31E 的续航和性能其实已经大打折扣，基本上只能跑办公的 Office 全家桶和浏览网页，因此我又拿到了家人换下来的 Dell XPS 13，作为我大学时期使用的电脑</p><p>这台电脑的配置如下：</p><ul><li>i7-5500U</li><li>8G DDR3 1600MHz</li><li>SAMSUNG SSD PM851 M.2</li></ul><p>而作为一个已经用惯了 Windows 11 的人，没了很多 Windows 11 独有的 Features，其实是用不太惯的，比如快捷控制中心的蓝牙连接，滚轮音量调节，资源管理器的多标签页…</p><p>所以，我想来想去，还是把这台电脑升级了 Windows 11</p><p>说起来，这个决定还是挺冒险的，毕竟这台电脑的官方支持是没支持 Windows 11 的，虽然他们的内核都是NT 10，其他部分的改动也不算太大，理论上很难会出现兼容性问题，不过，这可是我大学期间的主力电脑啊</p><p>如果说两年前尝试 Windows 11 是冒险者般的不顾一切的探索和开拓，那么两年后的这次尝试 Windows 11，则多了一份普通消费者的挑剔与衡量</p><p>为了测试这 Windows 11 是否真的能够成为我的主力系统，我在这次做了一下比较严谨的对比</p><h2 id="续航"><a href="#续航" class="headerlink" title="续航"></a>续航</h2><p>我首先测试的是电池续航，这也是一台笔记本是否能够被带出去的重要考量指标，在我使用 Windows 10 的期间，我大概可以完整耗尽电量的使用时长是8h（打字、浏览网页）。</p><p>到了 Windows 11，我实测看了2.5小时的视频耗电量为46%（因为测视频比较快），这续航和 Windows 10 相比较，最终差距基本仅有半小时左右，还算符合我的预期</p><h2 id="内存"><a href="#内存" class="headerlink" title="内存"></a>内存</h2><p>然后是内存占用情况，毕竟内存不够，系统默认会大量 swap 到硬盘，严重拖慢了系统运行速度，大部分旧机型的性能瓶颈也是在内存</p><p>实测下来，刚开机的时候，内存占用率是完全一样的，意味着差异仅约为10MB</p><p>但是随着系统各项服务和程序的自启动，两个系统的占用还是出现了一些差距：Windows 11 的小部件和 Windows Defender 总共会比 Windows 10 占用多出150MB左右，其实也还算是在可接受范围内吧</p><p>至于后续的优化，其实也并不是特别难，安装一个<code>能源之星X</code>，再在组策略里面禁用掉一堆的收集数据服务和小组件，占用就基本上和 Windows 10 一样了</p><h2 id="噪音"><a href="#噪音" class="headerlink" title="噪音"></a>噪音</h2><p>这个方面是我在之前从未想到过的，毕竟我平时在房间里就一个人基本上还会带上耳机，所以对噪音并不敏感</p><p>我到了大学我宿舍里面并不只有我一个人，过大的噪音，会打扰到同学的休息学习</p><p>在我对比后发现，Windows 11 在这方面对强行升级上来的老机型优化并不友好，即使 CPU 的占用率仅有7%，已经长时间没运行高负载的应用，手摸着电脑也不烫的情况下，风扇依然会以中等转速运转，和 Windows 10 中以低速运转甚至直接关闭风扇的调度策略截然不同。</p><p>正当我有点忍无可忍，准备想按下“回退到 Windows 10”的按钮时，我突然想到，既然我有这个问题，其他人也一定会有人同样受到这个问题的折磨，是我便上网搜索了一下，抢了一个其他的官方支持 Windows 11 的同品牌相似机型的驱动，强行打了上去，果然效果好多了</p><p>虽然最终的效果没有 Windows 10 那样过了1～2分钟就降速来得那么舒服（起码要5分钟），但是最起码的还是能把风扇速度降下来了，不会过于打扰到舍友了</p><hr><p>嗯，想完这么多，其实，只要肯研究，Windows 11 现存的问题还是大部分可以解决的，我也觉得暂时保留着这个 Windows 11 系统了（除非几天后真的发现了更加恶性的问题）</p>]]>
    </content>
    <id>https://haotian22.top/8d55ea46.html</id>
    <link href="https://haotian22.top/8d55ea46.html"/>
    <published>2023-09-09T16:40:18.640Z</published>
    <summary>在2021年首次体验那个并不完善的 Windows 11 的两年后的今天，再度把 Windows 11 当作主力机的系统，会是什么感受呢？</summary>
    <title>时隔两年，再度体验 Windows 11</title>
    <updated>2023-09-09T16:40:18.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>Hao_Tian</name>
    </author>
    <content>
      <![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>这件事还要从我拿到 iPad 但是还没开学的时候说起……</p><h2 id="初见-Drawthings"><a href="#初见-Drawthings" class="headerlink" title="初见 Drawthings"></a>初见 Drawthings</h2><p>近期因为上大学，家里给我准备了一台 iPad Air ，在还没开学的日子里，我就一直在思考：iPad 除了电子笔记之外还能有什么生产力的应用场景？</p><p>于是我便上 v2ex（一个技术交流论坛）上搜寻了许多关于 Ipad 如何用来提高生产力的相关文章，便看到了这个软件</p><p>我兴高采烈地把它从 App Store 下载了下来（应该不限账号区域的），因为我使用的电脑显卡并不算强，内存也不太够，所以从 Stable Diffusion 发布至今，我都还没有在自己的设备上尝试过本地跑图，对下载模型、模型之间的区别等等知识很多也只是一知半解</p><p>一打开软件，映入眼帘的就是一大堆模型的下载页面，有原版的  Stable Diffusion 1.x 2.x XL，也有热门的第三方模型，例如专画二次元的 Anything V3，下载完成就能像类似SD webui那样输入参数来画图了</p><p>Drawthings，利用的是苹果的 CoreML 功能，能调用专门的AI芯片来处理图片，因此即使在移动设备是出图的速度也能接受（512*768的 Anything V3 大概需要30s一张）</p><p>另外还是建议在充着电的时候再开始跑图，画图耗电量挺大的，一张图就能消耗掉1%-2%的电量</p><h2 id="深入使用"><a href="#深入使用" class="headerlink" title="深入使用"></a>深入使用</h2><p>在经过一段时间的使用后，我大概学习到了第三方模型、LORA、Embedded 提示词等用法</p><p>不过需要注意的是，在 Drawthings 上用来画图的模型与原版 SD webui 使用的模型并不完全相同，第三方模型导入后还得经过一次转换变成苹果 CoreML 专用的模型</p><p>由此，就诞生出了模型不兼容的问题</p><p>有些模型，在导入后，出图的效果十分模糊，512px的图片糊的像256px，连加上放大器没有明显改进，我测过有这种情况的模型有：Anything V5&#x2F;ink，AOM3A3，AOM3A2</p><p>以上的模型最好还是不要在 Drawthings 中使用</p><p>另外，因为转了一次模型，储存空间的占用增大也很明显，我明明只导入了10G左右的模型，占用却来到了17G（不包含导入前的原文件），各位需要预留足厚的储存空间</p>]]>
    </content>
    <id>https://haotian22.top/7dd8f297.html</id>
    <link href="https://haotian22.top/7dd8f297.html"/>
    <published>2023-08-30T04:25:44.615Z</published>
    <summary>iPad使用Drawthings来本地生成AI图片的初体验</summary>
    <title>真正的生产力：iPad的Drawthings</title>
    <updated>2023-08-30T04:25:44.615Z</updated>
  </entry>
</feed>
