<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0"><channel><title>光溯星河</title><link>https://blog.tsio.top</link><atom:link href="https://blog.tsio.top/rss.xml" rel="self" type="application/rss+xml"/><description>timeStarry's Blog</description><generator>Halo v2.21.9</generator><language>zh-cn</language><image><url>https://blog.tsio.top/upload/favicon.png</url><title>光溯星河</title><link>https://blog.tsio.top</link></image><lastBuildDate>Tue, 14 Apr 2026 04:47:18 GMT</lastBuildDate><item><title><![CDATA[认知常新：工作一年之后对 AI 时代计算机行业的再思考]]></title><link>https://blog.tsio.top/archives/Mu4eaq4s</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E8%AE%A4%E7%9F%A5%E5%B8%B8%E6%96%B0%EF%BC%9A%E5%B7%A5%E4%BD%9C%E4%B8%80%E5%B9%B4%E4%B9%8B%E5%90%8E%E5%AF%B9%20AI%20%E6%97%B6%E4%BB%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A1%8C%E4%B8%9A%E7%9A%84%E5%86%8D%E6%80%9D%E8%80%83&amp;url=/archives/Mu4eaq4s" width="1" height="1" alt="" style="opacity:0;">
<p>在与朋友的讨论中，我常常提及自己对未来的看法：我为行业的未来感到无比光辉，却认为个人的前景暂时晦暗。作为被称为“互联网时代原住民”的00后，我深刻体会到以计算机为核心的信息技术，对21世纪产生的颠覆性改变；本科及毕业后的短短几年里，我得以学习计算机并从事行业工作，对此，我深感荣幸。即便有一天我将不得不离开计算机行业，我也会主动去适应新的行业、新的形势，我已然接受这一切只是我漫长人生中一段精彩的经历，虽然心中仍会存有遗憾。</p>
<p>我始终坚信，程序员这一行业终将退出历史舞台，它本质上是计算机发展进程中一段无奈的过渡。正如个人电脑概念尚未普及的年代，人们曾普遍认为，使用计算机是少数技术人员的专属权利。而未来，软件开发、需求定制将彻底摆脱传统软件工程的束缚，用户发布创意产品，会像如今摄制、发布Vlog一样便捷高效，计算机行业也将在AI的引领下，真正步入成熟阶段。</p>
<p>纵观历史，任何一次技术革命都必然伴随恐慌与动荡，AI技术亦是如此。作为AI技术的重要分支，LLM如今正得到大力发展与广泛推广，即便仍处于初步发展与衍生阶段，就已轻松碾压了传统翻译、插画、语音等领域的相关工作。可以预见，随着相关工程难题的逐步破解，以及AI辅助工具的快速迭代，其对复杂工程、复杂任务的介入，必将迅猛且不可抵挡。也正因此，有人观察到一个有趣的现象：面对AI时代的冲击，首当其冲的程序员群体反而最为冷静——因为他们对AI的认知足够清晰，过去数十年间，他们始终身处技术快速迭代的浪潮中，深知这是不可逆转的时代大势。</p>
<p>但在这份冷静之外，我认为年轻群体更应多一份狂热，因为基于这份认知推导而出的未来，实在太过耀眼、太过令人向往。自信息技术革命以来，“计算机+Everything”的理念已深入人心，计算机早已成为所有标准工业数字化的核心基础。现代制造业借助计算机技术实现了标准化、规模化发展，众多实体资产也在这一过程中完成了数字化、虚拟化转型。</p>
<p>如今，计算机行业已站在智能化时代的分水岭，试问，谁又能认为自己或所处的行业可以独善其身？拒绝进步的一切，终将被时代淘汰，而这一次的淘汰速度，将远超历史上任何一次技术迭代！</p>
<hr>
<p>当然，做出这样的判断并非空穴来风，以我们当下的认知视角，能清晰看到三个核心趋势。</p>
<p>首先，AI的前景极为广阔，当前却存在一种认知误区——人们习惯以LLM的特征替代AI的全部概念。批评者片面认为，LLM的缺点就是未来AI时代的缺点，LLM的上限就是AI时代的上限。这种观点无疑是荒谬的，既不符合客观事实，也忽视了LLM工程发展的巨大潜力。以发展的眼光来看，如今人们关注的提示工程、记忆能力、Token费用、多模态融合、调用安全等问题，均可以通过工程技术解决，甚至无需借助前沿学术方向就能反驳这种片面观点。如今，大模型相关领域的技术正以周为单位快速迭代，每到周一坐到工位上，我们都能清晰感受到，上周的认知与观点已悄然过时。</p>
<p>其次，是关于软件资产的判断。过去数十年，随着互联网行业的飞速发展，软件资产迅速崛起，成为互联网资产舞台的核心。而如今，随着AI Coding深度融入开发流程，软件工程正受到前所未有的深刻重塑，软件将不再是核心资产。企业与组织的价值评估，将重新回归到更具象、更可感知的生产资料上，比如电力、算力等核心基础设施。</p>
<p>第三，计算机行业的技术迭代并非孤立存在。除了备受关注的AI技术，在社会目光较少聚焦的领域——航空航天、信息网络空间安全、生物医疗、电力电子、集成电路等，都在稳步迈上新台阶，或是突破前所未有的发展瓶颈。此外，当今世界正经历百年未有之大变局，地缘政治、全球贸易、科学技术等各个领域，都在承受前所未有的考验。未来的世界，迫切需要更先进的生产力、更前沿的社会组织形态，而计算机行业的迭代升级，正是支撑这一需求的核心力量。</p>
<hr>
<p>还有一个热门的话题，AI焦虑——本质上是社会旧思想与新技术剧烈碰撞的产物。</p>
<p>我们都有目共睹，在短短一周或稍长一段时间里，人们曾狂热地投身“养虾”热潮——无论是否理解计算机技术、是否了解AI，也无论“虾”是否真的能为自己提升生产力，人们都蜂拥而至。然而，这般狂热而来的人，也必将走马观花般匆匆离去，热潮褪去后，只留下满地“肉鸡”，徒留一阵喧嚣。</p>
<p>这样的场景，在过去每一次新技术出现时都曾上演，在未来也必将反复重现。但我们不能片面否定这类现象，而应辩证看待，更不可片面指责群众的认知水平。</p>
<p>就事论事而言，此次“养虾”热潮，恰恰彰显了全民教育素质的提升和算力基础设施的完善。面对AI时代的到来，人们正前所未有地拥有平等探索未来的机会，而探索的深度与广度，仅取决于个人的认知水平与探索意愿。同时我们也看到，工信部及相关安全应急单位迅速响应，面向社会大众科普AI相关安全知识，尽管最终效果仍有待观察与完善。</p>
<p>未来，旧思想与新技术的碰撞还会不断发生。从马克思主义视角来看，AI 作为新一代先进生产力的集中体现，其普及与应用终将推动生产关系乃至社会结构的深刻变革，技术进步的最终指向，也应是促进人的全面发展与社会公平的实现。在此基础上，AI 应用应积极探索更贴近群众生产生活的场景，企业组织、政府单位则应更主动地向群众科普 AI 技术，比当年推广计算机时更积极、更具创造力，从而全面提升全社会对 AI 的认知水平与应用能力，让新技术真正服务于每一个人。</p>]]></description><guid isPermaLink="false">/archives/Mu4eaq4s</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fqiniucloud.tsio.top%2Fblogfile%2F%25E6%259D%2582%25E8%25AF%259D%25E9%2597%25B2%25E8%25B0%2588-qbwdkqvc.png&amp;size=m" type="image/jpeg" length="0"/><category>技术博客</category><pubDate>Sun, 22 Mar 2026 15:32:00 GMT</pubDate></item><item><title><![CDATA[【辞旧迎新·2026丙午马年】春信策马，万事尽欢]]></title><link>https://blog.tsio.top/archives/OLQ1al6a</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E8%BE%9E%E6%97%A7%E8%BF%8E%E6%96%B0%C2%B72026%E4%B8%99%E5%8D%88%E9%A9%AC%E5%B9%B4%E3%80%91%E6%98%A5%E4%BF%A1%E7%AD%96%E9%A9%AC%EF%BC%8C%E4%B8%87%E4%BA%8B%E5%B0%BD%E6%AC%A2&amp;url=/archives/OLQ1al6a" width="1" height="1" alt="" style="opacity:0;">
<p>过去一年，于我而言是充满色彩的一年。我们或主动、或被动地被时间推着向前，所有期待与不期待之事，都按其客观规律发生着。回望这一程，没有奇迹，也没有崩塌，只有不断展开的现实与不断变化的一切。</p>
<p>25年初，我以一种近乎耗尽的状态结束了一段实习，随后进入春招。那段时间的基调是松弛而迟缓的——没有继续推进系统学习，也没有大规模投递。在这种近乎“放任”的节奏下，我很快获得了一份称不上理想、却足够安稳的春招实习，也正是如今这份工作的起点。</p>
<p>回头看，这个选择并非错误。许多同学在持续的高压与折磨中坚持到五六月，才终于获得更好的结果；而我，用三月的实习与四月的三方，换来了一个相对平静的春天。那时的我，确实太疲惫了。</p>
<p>年中，我们迎来了人生中重要的节点——大学毕业。</p>
<p>毕业之后，我时常陷入一种轻微的恍惚：我总觉得自己不该如此顺从地步入工作，继续深造的念头反复浮现。我难以相信，自己就这样离开了象牙塔，结束了十六年校园生活。一切都过于顺利：没有剧烈的不适，没有命运的波澜，一切都在预期之中。但也正因如此，某种隐隐的不安时常浮现。人们常说“不破不立”，而我似乎总在精确回避痛苦，用妥协换取稳定与普通的日子。理性告诉我这没有错，但内心却始终无法给出完全肯定的答案。</p>
<p>也许，这本身就是问题。</p>
<p>下半年，随着搬家与预算制度的建立，我的在京生活逐渐趋于稳定。周末若天气晴好，便去公园走走；若阴雨沉沉，则流连于博物馆之间。</p>
<p>与此同时，我开始零散地探索各种方向：职业路径、专业学科、科技史……然而这些学习始终碎片化、无体系。也正是在这段时间，我第一次清晰地感受到一种“知识的空洞”——我缺乏太多。
 <br>
 缺乏理论深度，也缺乏实践厚度。缺乏一套足以支撑我理解世界的结构。</p>
<p>我对世界充满兴趣，却不知从何处落笔。但我知道什么也不做肯定不是我想要的答案。</p>
<p>毕业以来，我常与朋友讨论行业与技术的未来。也许这些观点日后看来会显得稚嫩，但它们对当下的我而言仍不可或缺。我以为，面向未来，对于系统工程的认知和创造力将成为我们最重要的资产。许多人们今天仍然不以为意的东西、解决方案，都将在不远的将来，也许下个月，下个星期的“跨时代方案”中见到它们的影子。</p>
<p>面对以 AI 为引领的新一轮工业变革，我既期待，又敬畏。作为人类个体，我期待一次新的工业跃迁——历史上每一次技术革命，都曾深刻改写文明进程；而作为一名刚入行的工程从业者，我也清楚，在巨变时代，“代价”往往意味着淘汰与重塑。</p>
<p>我时而为自己的未来感到迷茫，又偶尔不自量力地忧虑技术与人类的命运。或许，这正是我们这一代人共有的情绪。</p>
<p>毕业后的半年，我用一份普通薪水在北京维持生活，略有积蓄。年前，我用掉所有调休与年假，为父母准备了新年礼物，并规划了一次昆明五天四夜的旅行。</p>
<p>这是我第一次带父母旅行，也是我们家的第一次真正意义上的家庭旅行。</p>
<p>昆明阳光明亮，滇池开阔，海鸥自由，米线温热，而最重要的是爸妈很开心。</p>
<p>这是一件极其纯粹的快乐之事，没有复杂情绪，没有现实压力，只有简单而真实的幸福。</p>
<p>对于新的一年，我并没有十足信心。前方依然是巨大的未知，甚至不亚于过去一年。未知令人恐惧，却也蕴含机遇。</p>
<p>于我而言，<strong>不破不立</strong>或许将成为这一年的核心关键词——它至少会成为我下一阶段规划与行动的思想基准。</p>
<p>过去一年的规划与实践虽不完美：规划粗糙，执行松散，但它们依然构成了重要的基础。新的一年，我将延续既有惯例，同时修补漏洞，直面个人问题，提升综合能力，在继承上一年实践的基础上继续推进职业能力、财务结构、系统学习、体能健康和思想认知各方面的进步。以此，我将建立更强的抗风险能力，为未来的不确定性保留更多可能。</p>
<p>同时，我希望持续探索在非脱产条件下稳步提升学科能力与认知深度的路径。</p>
<p>旧章已落，新页未书。</p>
<p>走吧，我们春天见！</p>]]></description><guid isPermaLink="false">/archives/OLQ1al6a</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Ffs.tsio.top%2Fblog%2F%25E9%25A9%25AC%25E5%25B9%25B4%25E5%25B0%2581%25E9%259D%25A2.png&amp;size=m" type="image/jpeg" length="0"/><category>随笔散记</category><pubDate>Wed, 18 Feb 2026 14:06:00 GMT</pubDate></item><item><title><![CDATA[【实践记录】N+1 查询问题：从一次跨地域接口超时说起]]></title><link>https://blog.tsio.top/archives/NBw0I8OL</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E5%AE%9E%E8%B7%B5%E8%AE%B0%E5%BD%95%E3%80%91N%2B1%20%E6%9F%A5%E8%AF%A2%E9%97%AE%E9%A2%98%EF%BC%9A%E4%BB%8E%E4%B8%80%E6%AC%A1%E8%B7%A8%E5%9C%B0%E5%9F%9F%E6%8E%A5%E5%8F%A3%E8%B6%85%E6%97%B6%E8%AF%B4%E8%B5%B7&amp;url=/archives/NBw0I8OL" width="1" height="1" alt="" style="opacity:0;">
<h1 id="n1-查询问题从一次跨地域接口超时说起">N+1 查询问题：从一次跨地域接口超时说起</h1>
<h2 id="1-什么是-n1-查询问题">1. 什么是 N+1 查询问题</h2>
<p><strong>N+1 查询</strong>是 ORM 和分层架构里非常常见的一类性能问题：为了得到一页主表数据，代码先执行 <strong>1 次</strong>查询拿到 N 条主记录，再在循环里对每条记录各查一次关联数据，一共执行 <strong>1 + N 次</strong>（甚至更多）数据库查询。</p>
<pre><code>1 次查询：获取审核记录列表（例如 10 条）
N 次查询：对每条记录分别查「主实体」「关联内容 A」「关联内容 B」「统计数量」……
→ 总查询次数 = 1 + 10 × 若干 = 几十次甚至上百次
</code></pre>
<p>在单次延迟很低的机房内网环境里，几十次查询可能还能接受；一旦数据库和应用<strong>跨地域部署</strong>（例如应用与数据库不在同一地域），每次往返 100ms+，总耗时就会轻松突破数秒甚至超时。下面用一次真实优化做说明。</p>
<hr>
<h2 id="2-业务背景ugc-内容审核列表在海外环境超时">2. 业务背景：UGC 内容审核列表在海外环境超时</h2>
<p>部署在国内的运营平台 UGC 内容审核后台有一个审核列表接口：按筛选条件分页拉取待审核/已审核记录，每条记录需要展示：</p>
<ul>
 <li>审核单基本信息</li>
 <li><strong>当前生效内容</strong>：主实体及其关联的配置、资源等（来自多张表）</li>
 <li><strong>最新提交内容</strong>：用户最新一次提交的配置、资源及创作者信息</li>
 <li><strong>统计信息</strong>：该条目的关联记录数量（用于区分新建还是编辑等）</li>
 <li>审核人姓名（来自另一套平台自身的用户/权限库）</li>
</ul>
<p>在<strong>海外某区域</strong>的生产环境下，该接口经常超时，前端列表迟迟出不来。排查后发现：<strong>应用（运营平台）与数据库（海外线上生产数据库）跨地域部署</strong>，网络延迟较大，而接口里存在严重的 N+1 查询，<strong>把高延迟放大了很多倍</strong>。</p>
<hr>
<h2 id="3-出问题的实践">3. 出问题的实践</h2>
<h3 id="31-列表接口的原始逻辑伪代码">3.1 列表接口的原始逻辑（伪代码）</h3>
<pre><code class="language-python"># 1 次查询：拿到本页的审核记录
audit_records, total = await get_audit_list_with_filters(...)

items = []
for audit_record in audit_records:   # 假设 10 条
    # 每条记录都触发多次数据库访问
    effective_content, latest_content = await get_content_versions(
        db, audit_record.entity_id
    )  # 内部：主实体 + 生效配置/资源 + 最新配置/资源 + 创作者 ≈ 6+ 次查询
    
    resource_count = await count_resources_by_entity_id(db, entity_id)   # 1 次
    config_count = await count_configs_by_entity_id(db, entity_id)     # 1 次
    
    item = build_item(audit_record, effective_content, latest_content, ...)
    items.append(item)

# 再批量查审核人姓名（这里本来还可以，但前面已经爆了）
auditor_name_map = await get_auditor_names(user_db, auditor_ids)
</code></pre>
<p>也就是说：<strong>每一条审核记录</strong>都会触发多次跨库/跨表查询。<code>get_content_versions</code>​ 内部还会按 <code>entity_id</code> 再去查主实体、生效配置、生效资源、最新配置、最新资源、创作者等，单条记录就是 6～8 次往返。</p>
<h3 id="32-量化为什么在跨地域环境下会超时">3.2 量化：为什么在跨地域环境下会超时</h3>
<p>假设：</p>
<ul>
 <li>每页 10 条审核记录</li>
 <li>每条记录 8 次数据库往返（保守估计）</li>
 <li>应用 ↔ 数据库单次 RTT ≈ 100ms（跨地域常见量级）</li>
</ul>
<p>则：</p>
<ul>
 <li>总往返次数 ≈ <strong>1（列表） + 10×8 = 81 次</strong></li>
 <li>仅网络延迟 ≈ <strong>81 × 100ms ≈ 8.1 秒</strong></li>
</ul>
<p>再加上 SQL 执行时间、序列化、下游调用等，很容易就超过前端或网关的超时时间，表现为「列表拉取超时」。</p>
<hr>
<h2 id="4-解决思路批量查询--并行">4. 解决思路：批量查询 + 并行</h2>
<p>思路很简单，把「循环里的单条查询」改成「先批量查，再在内存里组装」：</p>
<ol>
 <li>
  <p><strong>先收集本页用到的所有 ID</strong>
   <br>
   例如：所有 <code>entity_id</code>​、所有 <code>auditor_id</code>​、以及由主实体推导出的 <code>config_id</code>​、<code>resource_id</code> 等。</p>
 </li>
 <li>
  <p><strong>用少量几次“批量查询”一次性取回</strong></p>
  <ul>
   <li>按 <code>entity_id</code> 列表批量查：主实体、最新配置、最新资源、各表统计数量</li>
   <li>按 <code>config_id</code>​ / <code>resource_id</code> 列表批量查：生效配置、生效资源</li>
   <li>按 <code>creator_id</code> 批量查创作者</li>
   <li>审核人姓名：按 <code>auditor_id</code> 在用户库做一次批量查询</li>
  </ul>
 </li>
 <li>
  <p><strong>能并行的就并行</strong>
   <br>
   例如：6 个批量查询彼此无依赖，可以用 <code>asyncio.gather</code> 一次发出去，总耗时 ≈ 最慢的那一个，而不是 6 次相加。</p>
 </li>
 <li>
  <p><strong>在内存里组装</strong>
   <br>
   遍历本页的 <code>audit_records</code>​，从各个ID → 实体的 map 里取数据，拼成前端需要的结构，<strong>不再在循环里访问数据库</strong>。</p>
 </li>
</ol>
<p>这样，<strong>总查询次数从 1 + N×8 降为固定的 7～8 次</strong>，且其中 6 次可以并行，高延迟下总耗时主要取决于<strong>少数几次往返</strong>而不是几十次往返。</p>
<hr>
<h2 id="5-业务层批量查询实践">5. 业务层批量查询实践</h2>
<h3 id="51-crud-层提供批量查询">5.1 CRUD 层：提供批量查询</h3>
<p>在 CRUD 层为「按 ID 列表查」增加批量接口，避免在 service 里循环调用单条查询。例如：</p>
<pre><code class="language-python"># 批量查主实体
async def batch_get_entities_by_ids(db, entity_ids: List[int]) -&gt; dict[int, Any]:
    if not entity_ids:
        return {}
    result = await db.execute(
        select(MainEntity).where(MainEntity.id.in_(entity_ids))
    )
    return {row.id: row for row in result.scalars().all()}

# 批量查「每个实体的最新配置」（按 entity_id 分组取 max(id) 再 in 查）
async def batch_get_latest_configs_by_entity_ids(db, entity_ids) -&gt; dict[int, Any]:
    # 子查询：每个 entity_id 对应的最大 config.id
    # 再 in 查这些 id 的完整行
    ...

# 同理：batch_get_latest_resources_by_entity_ids
#       batch_get_configs_by_ids / batch_get_current_resources_by_resource_biz_ids
#       batch_count_resources_by_entity_ids / batch_count_configs_by_entity_ids
#       batch_get_users_by_ids（创作者）
</code></pre>
<p>这样，<strong>所有“按 ID 查一条”的地方，都改成“按 ID 列表查一批、返回 dict”</strong> ，由 service 在内存里做 O(1) 查找。</p>
<h3 id="52-service-层先批量拉取再组装">5.2 Service 层：先批量拉取，再组装</h3>
<p>列表接口的 service 逻辑可以改成类似这样（保留你项目里的命名和表结构即可）：</p>
<pre><code class="language-python"># 1. 拿到本页审核记录（1 次查询）
audit_records, total_count = await get_audit_list_with_filters(...)
entity_ids = list(set(r.entity_id for r in audit_records))

# 2. 批量查本页用到的所有数据（6 个批量查询，并行）
(
    effective_configs_map,
    effective_resources_map,
    latest_configs_map,
    latest_resources_map,
    resource_counts_map,
    config_counts_map,
) = await asyncio.gather(
    batch_get_configs_by_ids(db, effective_config_ids),
    batch_get_current_resources_by_ids(db, effective_resource_ids),
    batch_get_latest_configs_by_entity_ids(db, entity_ids),
    batch_get_latest_resources_by_entity_ids(db, entity_ids),
    batch_count_resources_by_entity_ids(db, entity_ids),
    batch_count_configs_by_entity_ids(db, entity_ids),
)

# 创作者、审核人同样：按 ID 列表各 1 次批量查询
creators_map = await batch_get_users_by_ids(db, creator_ids)
auditor_name_map = await get_auditor_names(user_db, list(auditor_ids))  # 内部改为 in 查询

# 3. 纯内存：遍历 audit_records，从各 map 里取数据组装 item
for audit_record in audit_records:
    entity = entities_map.get(audit_record.entity_id)
    effective_config = effective_configs_map.get(entity.current_config_id)
    # ...
    item = AuditRecordItem(
        ...
        resource_count=resource_counts_map.get(entity_id, 0),
        config_count=config_counts_map.get(entity_id, 0),
    )
    items.append(item)
</code></pre>
<p>要点：</p>
<ul>
 <li><strong>循环内不再出现</strong> <strong>​<code>await xxx(db, single_id)</code>​</strong> ，只有「从 dict 里 get」。</li>
 <li>与数据库的交互集中在「1 次列表 + 若干次批量 + 1 次审核人」，且批量之间用 <code>gather</code> 并行。</li>
</ul>
<h3 id="53-审核人姓名用户库也改成批量">5.3 审核人姓名：用户库也改成批量</h3>
<p>如果 <code>get_auditor_names</code>​ 原来是循环里按 <code>auditor_id</code> 逐条查用户，可以改为“一次 in 查询”：</p>
<pre><code class="language-python"># 用户 DAO 增加批量接口
async def get_users_by_ids(cls, db, user_ids: list[int]) -&gt; list[User]:
    if not user_ids:
        return []
    result = await db.execute(
        select(User).where(User.deleted == 0, User.id.in_(user_ids))
    )
    return list(result.scalars().all())
</code></pre>
<p>这样审核人姓名也只会产生 1 次用户库查询，而不是 N 次。</p>
<hr>
<h2 id="6-效果对比">6. 效果对比</h2>
<table>
 <thead>
  <tr>
   <th>指标</th>
   <th>优化前</th>
   <th>优化后</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>每页 10 条时的 DB 往返次数</td>
   <td>1 + 10×8 ≈ 81 次</td>
   <td>1 + 6（并行） + 1 ≈ 8 次</td>
  </tr>
  <tr>
   <td>仅网络延迟（100ms/次）</td>
   <td>~8.1s</td>
   <td>~0.8s（含并行）</td>
  </tr>
  <tr>
   <td>接口总耗时</td>
   <td>易超时</td>
   <td>明显缩短，可稳定在 2～3 秒内</td>
  </tr>
 </tbody>
</table>
<p>（典型值）</p>
<hr>
<h2 id="7-小结如何避免和排查-n1">7. 小结：如何避免和排查 N+1</h2>
<ol>
 <li>
  <p><strong>写列表/详情接口时</strong>
   <br>
   凡是要“根据本页/本批 ID 去查关联表”的，优先想：<strong>能不能先收集 ID，再 in 查一批，最后在内存里组装？</strong> 避免在 for 循环里 <code>await</code> 任何按单 ID 查的接口。</p>
 </li>
 <li>
  <p><strong>CRUD 层</strong>
   <br>
   除了“按主键查一条”，尽量提供“按 ID 列表查一批”的接口，返回 <code>dict[id -&gt; 实体]</code>​ 或 <code>list</code>，方便 service 做 O(1) 查找。</p>
 </li>
 <li>
  <p><strong>无依赖的多次查询</strong>
   <br>
   用 <code>asyncio.gather</code> 并行，总耗时 ≈ max(各查询)，而不是 sum。</p>
 </li>
 <li>
  <p><strong>跨地域、高延迟</strong>
   <br>
   延迟越大，N+1 的惩罚越重。在这种环境下，<strong>减少往返次数</strong>往往比“单次 SQL 再优化”更关键，此时倾向于将查询合并的处理从SQL提到业务代码中来进行合并。</p>
 </li>
 <li>
  <p><strong>排查方式</strong></p>
  <ul>
   <li>看日志/APM 里该接口的 <strong>SQL 条数</strong> 和 <strong>总耗时</strong>；</li>
   <li>若“SQL 条数 ≈ 1 + 每页条数 × 常数”，基本就是 N+1；</li>
   <li>再在代码里找“循环 + await 查库”的写法，改为批量 + 内存组装。</li>
  </ul>
 </li>
</ol>
<hr>
<h2 id="参考阅读">参考阅读</h2>
<ul>
 <li><a href="https://www.sitepoint.com/silver-bullet-n1-problem/">The N+1 Queries Problem (Ruby/ActiveRecord)</a></li>
 <li><a href="https://docs.sqlalchemy.org/en/20/orm/queryguide/relationships.html">SQLAlchemy: Loading Relationships — joinedload / selectinload</a></li>
 <li><a href="https://zhuanlan.zhihu.com/p/27323883">什么是ORM中的N+1 - 知乎</a></li>
 <li>若使用 Django：<code>select_related</code>​ / <code>prefetch_related</code> 就是在解决同类问题。</li>
</ul>]]></description><guid isPermaLink="false">/archives/NBw0I8OL</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Ffs.tsio.top%2Fblog%2FN%2B1%25E6%259F%25A5%25E8%25AF%25A2%25E9%2597%25AE%25E9%25A2%2598.png&amp;size=m" type="image/jpeg" length="443625"/><category>技术博客</category><pubDate>Sat, 7 Feb 2026 13:53:00 GMT</pubDate></item><item><title><![CDATA[没有理由不记下这样一个可爱的日子]]></title><link>https://blog.tsio.top/archives/EOh7iZPD</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E6%B2%A1%E6%9C%89%E7%90%86%E7%94%B1%E4%B8%8D%E8%AE%B0%E4%B8%8B%E8%BF%99%E6%A0%B7%E4%B8%80%E4%B8%AA%E5%8F%AF%E7%88%B1%E7%9A%84%E6%97%A5%E5%AD%90&amp;url=/archives/EOh7iZPD" width="1" height="1" alt="" style="opacity:0;">
<p>照例，我会在周末补补觉，所以会起的晚些。所以看来这个周末也是一个普通的周末了，在睡眠与纠结中度过两天，而后开始一个新的工作周。</p>
<p>拉开窗帘的时候已经九点过了，视线沿着拉开的窗帘缝下探，挪到小区地下停车场的出口顶棚上，发现边缘似乎残留着些白霜。又看到干枯的草地略微有些泛白，联想到昨天下班时候同事说这周末会下小雪才确信昨夜应该是下雪了。目光回到窗前，又突然发现有些飞灰在窗前游荡，不紧不慢，定眼一看，竟是还未休止的雪，不过实在微小缓慢得可怜。这似乎预示着这并非一个寻常的周六。</p>
<p>简单洗漱后决心按照母亲的建议进行一场大扫除。这要从北京的干燥多灰开始说起，从实习算起，我在北京居住也差不多一年了，一年时间里无时无刻不在和房间里的灰尘斗争。这些飞灰靠扫帚是解决不了的，先不说能不能清扫干净，就算第一天扫了，第二天也能飞快覆盖各种表面。你要是直接摆烂不扫了，它们还会在各种角落堆积成絮状，看起来更难受了。所以我在母亲的指导下整了一个喷壶，简单清扫后，对地面一顿喷洒，然后拖地，看起来效果是不错的。</p>
<hr>
<p>下午出门，和朋友约好了完善去听音乐会，让我们这些个“下里巴人”鉴赏下高雅艺术，也顺带见识下高雅场所——国家大剧院。音乐会19:30开始，于是我们打算先在附近找个地方溜达，吃过饭再去。</p>
<p>我们从琉璃厂出发，沿着胡同一路瞎溜达，走到半路突然感觉有什么东西在眼前飘飞，又下雪了！和上午的雪性状看起来大差不差，这时候我们还没有意识到这将是一场多么少见的、漂亮的“鹅毛大雪”。朋友提出想去吃南门涮肉，听这个响亮的招牌很久了，我也提到下雪天正好适合吃涮肉，于是我们便奔着最近的前门涮肉店去了。</p>
<p>我们边吃边聊，大概五点过的时候，我回头望店门外，天还亮着，但是雪已经肉眼可见的大了，于是我向朋友提出一会儿可以从前门大街步行去国家大剧院，半个多点儿，正好赏雪了。</p>
<p>从涮肉店出门的时候，街上的雪已经很大了，大有“燕山雪花大如席”的阵仗，各种街面装饰物的表面也都堆上了积雪，我靠近一片积雪一看，就发现这场雪的雪花粒粒分明，冰晶形状完美且持久，天上飘得雪也并不急促，但显得很大。我一边向朋友感叹在东北四年都没见到这样的雪，一边拿出手机哐哐一顿拍。</p>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Ffs.tsio.top%2Fblog%2FIMG_20260117_175906.jpg&amp;size=m" alt="IMG_20260117_175906.jpg"></p>
<p>周日看到中气爱的视频才知道，这是一场难得的“干雪”，降水期间气温低、1000-3000米的高度范围内水汽充足，形成冰晶的雪花长期保持外形，在下落过程中不融化、与其它雪花组合形成更大片的雪花，打出了北方地区都罕见的超48的积雪比，让一场小雪下出了鹅毛大雪的的气势。</p>
<p>多么幸运，在一个恰好决定出门的下午遇见这样可爱的雪，恰到好处的雪！</p>
<hr>
<p>国家大剧院的画风说实话和周围的人民大会堂及中南海有点反差，一个巨大的半球面在RGB灯光的照映下并没有体现令人惊叹的美感，由于微风吹起了人造湖面的水波，映照而成的蛋形也未能看出。进到内部之后发现内饰装潢倒是充满了艺术感，音乐厅、歌剧院、小剧场的流线控制非常合理，出入场的人流不会冲突，布局也很克制科学，与简单的半球外观形成了反差。</p>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Ffs.tsio.top%2Fblog%2FIMG_20260117_183737.jpg&amp;size=m" alt="IMG_20260117_183737.jpg"></p>
<p>我和朋友在考虑预算和小红书的指导下，选择了较为便宜的音乐厅二层楼座一排的座位。我首先要吐槽这个楼座一个巨大的毛病，完全不考虑人类这种大体积生物坐下需要的空间！当我和朋友抱着羽绒服坐下的一瞬间就开始感慨接下来两个小时的坐姿恐怕要相当痛苦了。楼座一排的座位到二楼扶手墙壁的宽度仅仅恰好够一个成年人正面直立，坐下之后膝盖就完完全全绷死在墙前，脚踝还只能以一种及其别扭的方式歪着，这种痛苦是极其令人崩溃的。</p>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Ffs.tsio.top%2Fblog%2FIMG_20260117_190955.jpg&amp;size=m" alt="IMG_20260117_190955.jpg"></p>
<p>坐下之后我开始观察起整个音乐厅，抬头看向屋顶，是一片像自然风化后的波纹状石质纹饰，大厅灯光暗下后，又呈现出一种难以辨别的油画质感，光影明暗交替看起来更像是油墨深浅处。往大厅正面中间看，一眼望过去以为是某种条状现代风墙壁装饰，仔细一看才发现是一面巨大的管风琴，这又让我生出一个想要听一次管风琴演奏的愿望来，我想一定是很震撼的。</p>
<p>而后我开始观察起楼下的缓缓入场的人们，大家衣着各样，唯独没有影视剧里那样西装革履的“老欧洲贵族”形象，也许是这场次规格还不够，也许是那套古旧的打扮不流行了，不过也不由得让人感慨，新时代的发展、现代社会的生产力和人们对文艺的宽容度，足以让我们这样的普通工人群众以偶尔尝鲜的心态在大剧院见识这样一场高雅的音乐会真是一件不容易的事情。</p>
<p>至于演出内容嘛，音乐会结束后我和朋友达成一致意见，我们俩的艺术造诣实在有限，等到哪次有公众耳熟能详世界名曲的乐章或格局我们再来凑这种场合的热闹吧。音乐会的第一个部分着实给我听迷糊了，在曲目上写着叫“前奏与赋格”，可我的耳朵实在不服这个东西，听起来别扭极了，呕哑嘲哳难为听也不过如此了。</p>
<p>不过后面的表演曲目倒是让人舒服多了，分别是“D大调小提琴协奏曲，作品35”和“E小调第五交响曲，64”，前者有一股浓厚且非常刻板的欧洲老骑士在寒带田野间骑马溜向小风车的既视感，后者则充满了战争配乐的史诗感，能让我这木头耳朵都品出些令人激动的层次来。中间还有宁峰老师的巴赫独奏表演，也很有味道，会有些许让我想换个新耳机的冲动。</p>
<hr>
<p>晚上回家的路上，雪还在下，路边的积雪已经可以覆没一半鞋子了，这对于一场小雪来说实在惊人。我是万分喜爱这样的雪夜的，特别是今天这样一场蓬松大雪，吸音效果极好。其中我有特爱在这样的雪夜安静、漫无目的地在雪地里散步，踩在雪上也几乎不发出任何声音，抬头又可以看见比寻常夜晚更远的视野，因为白雪反射了光，宁静，由视觉与听觉共同组成的，无边无际的宁静，带着低温，大脑感到前所未有的舒适。</p>
<p>这样的雪夜，最大的缺点也就是交通和户外生产的灾难了，好在现代社区的组织力和工业设备的生产力一同发力下，道路清雪也并不是一件洪水猛兽之事了，像我们这样的普通人也可以更安心地享受大雪后这份难得的宁静了。</p>]]></description><guid isPermaLink="false">/archives/EOh7iZPD</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Ffs.tsio.top%2Fblog%2FIMG_20260117_175906.jpg&amp;size=m" type="image/jpeg" length="4824510"/><category>随笔散记</category><pubDate>Sun, 18 Jan 2026 12:12:00 GMT</pubDate></item><item><title><![CDATA[我曾以为只要我躲得够好，那些悲伤和艰苦就追不上我]]></title><link>https://blog.tsio.top/archives/7JL5zfXx</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E6%88%91%E6%9B%BE%E4%BB%A5%E4%B8%BA%E5%8F%AA%E8%A6%81%E6%88%91%E8%BA%B2%E5%BE%97%E5%A4%9F%E5%A5%BD%EF%BC%8C%E9%82%A3%E4%BA%9B%E6%82%B2%E4%BC%A4%E5%92%8C%E8%89%B0%E8%8B%A6%E5%B0%B1%E8%BF%BD%E4%B8%8D%E4%B8%8A%E6%88%91&amp;url=/archives/7JL5zfXx" width="1" height="1" alt="" style="opacity:0;">
<p style="">在旁人眼里，我总是乐观豁达的。我擅长在每一个岔路口快速识别出那个最轻松、最令人愉悦的选项——也就是所谓的“局部最优解”。我用这种方式屏蔽了痛苦，绕过了那些需要咬牙坚持的时刻，甚至刻意压低了脑海里那个喊着“去走难而正确的路”的声音。</p>
<p style="">常言道，躲得过初一，躲不过十五。那么现在，十五快要到了。</p>
<p style="">我不愿意承认，但不得不承认：这场离开象牙塔后的真正审判，来得比我预想的要快得多。</p>
<p style="">站在这个路口，我无法再祈求好运，也无法再依赖“局部最优”的策略来蒙混过关。无论是向前的丛林法则，还是回头的背水一战，都是我必须补上的课程。</p>]]></description><guid isPermaLink="false">/archives/7JL5zfXx</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fqiniucloud.tsio.top%2Fblogfile%2F%25E6%259D%2582%25E8%25AF%259D%25E9%2597%25B2%25E8%25B0%2588-qbwdkqvc.png&amp;size=m" type="image/jpeg" length="0"/><category>随笔散记</category><pubDate>Sun, 30 Nov 2025 15:29:00 GMT</pubDate></item><item><title><![CDATA[关于时间的困惑]]></title><link>https://blog.tsio.top/archives/HJ2yJKyi</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E5%85%B3%E4%BA%8E%E6%97%B6%E9%97%B4%E7%9A%84%E5%9B%B0%E6%83%91&amp;url=/archives/HJ2yJKyi" width="1" height="1" alt="" style="opacity:0;">
<p style=""><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">工作之后常常会听到朋友在周末的晚上向我抱怨周末过的快，或者在难得假日的最后一天抱怨这个假期真快之类。“过的快”，我是认同的，但这时候我往往会在心里补一句“工作日也不慢”。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">大概从中学时候起，我意识到时间过得很快很快，快到我们的一切感情都可以被它抹平，一切行为都在时间的尺度下变得无趣。以至于我们在看待时间这个毫无感情的物理量时，总会忍不住上升到哲学的高度，就像是在时间压制下一切无趣的事物中，衬托得时间本身倒是有趣了些，可以提供茶余饭后的谈资。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">时间从来不知道有后退的选项，它就像一个检票员，严肃地守在闸机口，对每一个通过的事件盖下“历史”的印戳，而后再也不见。你若是想要让它流连，这位检票员只会平静而威严地催促着前进，低声几句后，你再不能反驳。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">因此，我们在讲起过去的事情时，总会带着几分淡漠，哪怕是再深刻的事情，哪怕是死亡……这些淡漠似乎并不是我们主动带上的，但我们也无权拒绝。时间的声音是那样平静、无感却充满威严，不可抗拒。</span></p>
<hr>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">上周末看到杨振宁先生离世的消息，我平静许久的内心又涌出一股难言的感情，像是遗憾、震惊又有些许的困惑，困惑持续到了今天。作为大时代下的普通人，我们时常会因为这样或那样的消息感到心情的波动，在这个变革的时代，值得人们心情波动的事情很多，能让人们停下为之思考的东西却并不多。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">我的困惑来源于我熟悉世界的标杆又倒下了一根。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">从出生起，我们在父母、社会、学校的引导下建立对这个世界的认知，将一个个我们认定为价值的人事物作为我们世界认知的标杆，建立一个庞大的认知体系。而在我们成长过程中又不得不面对儿时世界的崩解，新观念的重塑，世界正在变得越来越不熟悉，又不得不越来越认真地去熟悉这个世界。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">大学时候，我曾经看到过一个非常有趣的观点，原话我不记得了，意思大概是说：</span></p>
<blockquote>
 <ul>
  <li>
   <p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)"><em>人们很容易接受自己少年时期已有的事物，认为它们就是世界本身的样子，一切都是理所应当的。</em></span></p>
  </li>
  <li>
   <p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)"><em>人们很乐意接受青壮年时期发明的事物，认为它们都是进步的、会改变世界的、革命性的。</em></span></p>
  </li>
  <li>
   <p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)"><em>人们反感自己年老时产生的一切事物，认为它们中的科技是会摧毁世界的、它们中的观念是大逆不道的。</em></span></p>
  </li>
 </ul>
</blockquote>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">这个说法很有意思，在漫漫时间长河之中，我们普普通通的一生何其短暂，却又何其冥顽不化。说简单一点，我想是“江山易改本性难移”，这个本性便是青少年时代形成的观念。时间在悄无声息中完成了这一切，在极短的时间内固化一个人的认知，又用弹指一挥之力让人在毫无知晓中过完了自己顽固的一生。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">当然，我们当中有一些充满智慧的人有能力突破时间的禁锢，在他们优秀的一生中都在与时俱进，时间无法固化他们的思想，杨老先生就是这样一个人。</span></p>
<hr>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">当然，也许有一天物理学家，数学家或是生物学家会得出一条结论，那就是一切淡漠的罪魁祸首并不是时间，而是我们人类早产的、脆弱的神经系统。让我们的记忆体系“对过去、当下和未来充满执念”又同时对过往的一切不再同情。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">科普UP主毕导有一个关于对数的科普视频令我印象深刻，视频用对数揭示了为什么我们会感觉时间越来越快，并推导结论“人生的中点其实是18岁”。我以浅薄的数学知识深刻认同这一结论，并乐意以人生的厚度去理解它。数学原理不在这里讨论，只是令人对时间再多了几分敬畏。</span></p>
<p style="line-height: inherit"><span fontsize="" color="var(--blur-text-color)" style="color: var(--blur-text-color)">回到最初的讨论，我们的感知也正是如此，对数的时间足以抹平我们的一切感情。人对一切充满了感情，最后却不得不在时间的面前平淡地面对一切。恐惧、困惑、遗憾都在一瞬间灰飞烟灭，而时间依然无所顾忌，毫不在意。</span></p>
<p style="line-height: inherit"><span fontsize="" color="">无月之夜，常有瞎想，没有逻辑，这种文字少有发出来的，但改了就不是本意了，凑合看吧。</span></p>
<p style="">
 <br>
</p>]]></description><guid isPermaLink="false">/archives/HJ2yJKyi</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fqiniucloud.tsio.top%2Fblogfile%2F%25E6%259D%2582%25E8%25AF%259D%25E9%2597%25B2%25E8%25B0%2588-qbwdkqvc.png&amp;size=m" type="image/jpeg" length="0"/><category>随笔散记</category><pubDate>Wed, 22 Oct 2025 15:04:00 GMT</pubDate></item><item><title><![CDATA[思源笔记API-MCP知识库方案]]></title><link>https://blog.tsio.top/archives/douBj4HD</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E6%80%9D%E6%BA%90%E7%AC%94%E8%AE%B0API-MCP%E7%9F%A5%E8%AF%86%E5%BA%93%E6%96%B9%E6%A1%88&amp;url=/archives/douBj4HD" width="1" height="1" alt="" style="opacity:0;">
<p>最近在研究把思源笔记作为主要笔记平台，发现思源笔记社区和API扩展性做得都很好，这很难不让人将它把知识库机器人结合起来。特别是对于主题化的学习笔记、网页收藏和工作归档这类笔记，使用大模型为其提供综合归纳、文档查询、历史问答等功能是很有吸引力的。</p>
<h2 id="实现思路">实现思路</h2>
<p>经过简单的搜索，主要归纳出三个方向：</p>
<ul>
 <li>思源自带的AI能力或插件：<a href="https://siyuannote.com/article/1724604650">人工智能 AI | 思源笔记用户指南</a>；</li>
 <li>使用SQLite MCP server，直接访问思源笔记数据库：<a href="https://zhuanlan.zhihu.com/p/26374360360">思源笔记+Cursor+MCP——打造你的个人专属 AI 资料库（AI搜索笔记、内容总结、大纲凝练、RAG搜索） - 知乎</a>；</li>
 <li>基于思源笔记API的MCP server：<a href="https://github.com/leolulu/siyuan-mcp-server">leolulu/siyuan-mcp-server</a>；</li>
</ul>
<p>首先是思源内置AI，配置完成后可以进行文档编辑，它的主要设计思路还是文本生成、扩写这一类。也有朋友告诉我实际体验一般，同时和我们建设知识库的需求不一致。</p>
<p>然后就是SQLite MCP方案，看起来很有意思。不过这个方案我没有细究，这个方案需要思源笔记客户端在本地打开，然后访问其数据库，而我主要是远程web部署，有兴趣的朋友可以跳转上面文章链接自行浏览。</p>
<p>那么接下来对我来说最优雅的实现方案就是思源开放API + MCP server + MCP客户端了。这个工作已经有很多人做过了，大部分是js实现，我列出这个版本是Python项目，可以去Lobehub自行搜索siyuan获得各种实现。</p>
<h2 id="实验步骤">实验步骤</h2>
<p>这个Python项目中的实现应该是可以在Lobechat中直接下载运行并配置的，不过我对源代码进行了简单的修改，所以选择本地手动安装运行。</p>
<p>MCP客户端JSON：</p>
<pre><code class="language-json">{
  "mcpServers": {
    "siyuan": {
      "command": "uv",
      "args": ["run", "siyuan-mcp-server"],
    }
  }
}
</code></pre>
<p>由于环境的特殊性，我将env挪到了<code>.env</code>文件中，然后修改了启动方式，采用本地conda运行，只需修改command和args对应内容即可。同时将URL修改为从环境变量获取。</p>
<p>经测试cursor、lobechat都可以检测到对应工具，不过对于问答的实际回答效果非常依赖模型能力。在cursor下gpt-5面对文件查找、模糊询问等问题都可以正确回答，需要多轮调用。在lobechat下deepseek-r1进行了长思考和多轮调用也找到了正确答案，但gpt4-turbo往往难以给出答案，其MCP调用内容仅限一轮，一次调用搜索尝试无结果后即终止该回答，效果很差。</p>
<p>因此需要注意，这类问题需要选择支持长消息、多轮对话、思考和方法调用的模型。</p>
<p>下面是deepseek-r1的调用效果：</p>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Ffs.tsio.top%2Fblog%2Fimage-20251015164245-9e8bbr1.png&amp;size=m" alt="deepseek-r1结合MCP调用测试"></p>
<p>更多玩法等待探索。</p>]]></description><guid isPermaLink="false">/archives/douBj4HD</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Ffs.tsio.top%2Fblog%2F%25E6%2580%259D%25E6%25BA%2590%25E7%259F%25A5%25E8%25AF%2586%25E5%25BA%2593.png&amp;size=m" type="image/jpeg" length="0"/><category>技术博客</category><pubDate>Wed, 15 Oct 2025 11:12:00 GMT</pubDate></item><item><title><![CDATA[【实践记录】快速构建一套好用的zsh环境]]></title><link>https://blog.tsio.top/archives/hOh87zA9</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E5%AE%9E%E8%B7%B5%E8%AE%B0%E5%BD%95%E3%80%91%E5%BF%AB%E9%80%9F%E6%9E%84%E5%BB%BA%E4%B8%80%E5%A5%97%E5%A5%BD%E7%94%A8%E7%9A%84zsh%E7%8E%AF%E5%A2%83&amp;url=/archives/hOh87zA9" width="1" height="1" alt="" style="opacity:0;">
<h2 id="环境介绍">环境介绍</h2>
<ul>
 <li>操作系统：Debian 12 (Bash)</li>
 <li>网络环境：良好（复杂网络环境下推荐参考各类教程中的gitee.com或其它镜像站）</li>
</ul>
<h2 id="操作步骤">操作步骤</h2>
<p>详细配置和插件特性参考官方文档，连接至<a href="https://blog.tsio.top#%E5%8F%82%E8%80%83%E9%98%85%E8%AF%BB">参考阅读</a>。</p>
<p><strong>01 安装zsh并下载插件资源</strong></p>
<pre><code class="language-shell"># 安装zsh并切换默认shell
sudo apt install -y zsh
chsh -s /bin/zsh

# 安装ohmyzsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# 主题 powerlevel10k
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
# 高亮插件
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting
# 自动提示
git clone https://github.com/zsh-users/zsh-autosuggestions.git $ZSH_CUSTOM/plugins/zsh-autosuggestions
# 基于频率的cd简化插件
git clone https://github.com/rupa/z.git $ZSH_CUSTOM/plugins/z
# 模糊查找工具
git clone https://github.com/Aloxaf/fzf-tab ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/fzf-tab
# fzf-tab依赖fzf工具，apt仓库中的fzf版本过于老旧，推荐前往仓库realease自行下载解压并放入Bin
# https://github.com/junegunn/fzf/releases
</code></pre>
<p><strong>02 编辑配置文件</strong></p>
<pre><code class="language-shell">vim ~/.zshrc
# 解开第二行的 export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH
# 修改主题ZSH_THEME="powerlevel10k/powerlevel10k"
# 找到plugins部分，添加插件配置如下：
plugins=(git
        zsh-syntax-highlighting
        zsh-autosuggestions
        z
        fzf-tab
)

# 推荐添加fzf快捷操作
[ -f ~/.fzf.zsh ] &amp;&amp; source ~/.fzf.zsh
alias f="fzf --height 80% --preview 'bat --style=numbers --color=always --line-range :500 {}' --preview-window right,70%,border-horizontal"
</code></pre>
<p>退出并保存。</p>
<p><strong>03 完成安装</strong></p>
<pre><code class="language-shell">source ~/.zshrc
# 然后跟随powerlevel10k的安装指引进行配置，完成配置后还可进入配置文件重新修改
</code></pre>
<h2 id="推荐工具">推荐工具</h2>
<ul>
 <li><a href="https://cn.x-cmd.com/">X-CMD | 模块化构建的命令行工具集, 提供 1000+ 轻量且高效的命令, 秒级启动</a></li>
 <li><a href="https://github.com/TheR1D/shell_gpt">TheR1D/shell_gpt: A command-line productivity tool powered by AI large language models like GPT-4, will help you accomplish your tasks faster and more efficiently.</a></li>
 <li><a href="https://github.com/aristocratos/btop">aristocratos/btop: A monitor of resources</a></li>
 <li><a href="https://github.com/fastfetch-cli/fastfetch">fastfetch-cli/fastfetch: A maintained, feature-rich and performance oriented, neofetch like system information tool.</a></li>
</ul>
<h2 id="参考阅读">参考阅读</h2>
<ul>
 <li>主题：<a href="https://github.com/romkatv/powerlevel10k">romkatv/powerlevel10k: A Zsh theme</a></li>
 <li>fzf：<a href="https://github.com/junegunn/fzf">junegunn/fzf: :cherry_blossom: A command-line fuzzy finder</a></li>
 <li><a href="https://leo03w.github.io/2022/09/23/ZSH%20%E9%85%8D%E7%BD%AE/">ZSH配置 - Leo Blog</a></li>
</ul>
<p>‍</p>]]></description><guid isPermaLink="false">/archives/hOh87zA9</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%258D%259A%25E5%25AE%25A2%25E9%2580%259A%25E7%2594%25A8%25E5%25B0%2581%25E9%259D%25A2%25E5%259B%25BE.png&amp;size=m" type="image/jpeg" length="54557"/><category>技术博客</category><pubDate>Thu, 25 Sep 2025 09:46:00 GMT</pubDate></item><item><title><![CDATA[【MySQL】一次SQL空格引发的“灵异事件”的排查解决]]></title><link>https://blog.tsio.top/archives/9ucPekHi</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E3%80%90MySQL%E3%80%91%E4%B8%80%E6%AC%A1SQL%E7%A9%BA%E6%A0%BC%E5%BC%95%E5%8F%91%E7%9A%84%E2%80%9C%E7%81%B5%E5%BC%82%E4%BA%8B%E4%BB%B6%E2%80%9D%E7%9A%84%E6%8E%92%E6%9F%A5%E8%A7%A3%E5%86%B3&amp;url=/archives/9ucPekHi" width="1" height="1" alt="" style="opacity:0;">
<p>起因是运营同学反馈在<strong>测试环境</strong>和<strong>线上环境</strong>配置了一套相同的数据，但该数据在测试环境可以正常显示，线上却无法正常显示。而当时测试环境与线上环境的代码已经同步，初步检查数据后也确实没有发现配置错误或异常日志。</p>
<p>但整个代码筛选过滤条件也不多，一个个看也没发现任何一个地方会导致过滤失效的。这时同事敏锐地发现配置中有一个空项，其值并非<code>NULL</code>，而是空格，这在数据库开发中算常见的错误了。于是尝试直接在线上环境将其置为NULL后查看接口返回数据，正常了。</p>
<p>问题就这么简单吗？</p>
<p>首先要强调，将空值统一定义为NULL或空数据结构是必要且科学的，可以避免上述情况的出现。但是，问题就在但是后，测试环境同样是空格，对于同一套代码，为什么测试环境可见？</p>
<p>抛开宇宙射线爆发导致比特翻转、玄学力量导致机魂不悦等重大因素后，疑点落到了SQL查询上。DEBUG发现，这套SQL在测试环境和正式环境返回了不同的结果！</p>
<p>在测试环境下执行以下SQL：</p>
<pre><code class="language-sql">SELECT '' = ' '; -- 返回 0 (false)
select support_language, support_language = '' , ' '='', LENGTH(support_language)  from mind_craft_characters where id=57; -- 返回值见下表
</code></pre>
<pre><code class="language-SQL">-- 补充嫌疑字段的DDL定义
`support_language` varchar(255) DEFAULT '' COMMENT '支持语言 [''zh'',''ja'',''en''],空表示都支持'
</code></pre>
<table>
 <thead>
  <tr>
   <th>support_language</th>
   <th>support_language = ''</th>
   <th>' ' = ''</th>
   <th>LENGTH(support_language)</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td></td>
   <td>1</td>
   <td>0</td>
   <td>1</td>
  </tr>
 </tbody>
</table>
<p>如你所见，<code>support_language</code>字段长度为1，包含一个空格，MySQL认为其等价于<code>''</code>，但在SQL中写下的空格并不认为<code>' '</code>和<code>''</code>是一个东西。那么线上环境无法筛出该数据的原因也呼之欲出了，线上环境执行该SQL得到的<code>support_language = ''</code>结果为<code>false</code>，那么这是为什么呢。</p>
<p>两个环境部署的MySQL版本均为<code>8.0.x</code>几乎可以排除由MySQL版本更新带来的策略不同问题。</p>
<p>经过反复的测试和对AI的拷打，同事从一大堆答案中找到了一句可靠的差异：<strong>排序差异</strong>。</p>
<blockquote>
 <p>在 MySQL 中，字符比较的行为取决于<strong>排序规则</strong>（<code>COLLATE</code>），不同的规则对字符的等价性有不同定义。你提供的两个查询结果差异源于 <strong>​<code>utf8mb4_general_ci</code>​</strong> 和 <strong>​<code>utf8mb4_0900_ai_ci</code>​</strong> 对空格的权重处理不同。</p>
</blockquote>
<p>验证实验SQL：</p>
<pre><code class="language-sql">SELECT
  HEX(WEIGHT_STRING(' ' COLLATE utf8mb4_general_ci)),   -- 返回空（权重被忽略）
  HEX(WEIGHT_STRING(' ' COLLATE utf8mb4_0900_ai_ci));   -- 返回非空（明确权重）
</code></pre>
<p>结论似乎可以揭晓了。</p>
<h2 id="最终结论">最终结论</h2>
<table>
 <thead>
  <tr>
   <th>排序规则</th>
   <th>权重值 (HEX)</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>​<code>utf8mb4_general_ci</code>​</td>
   <td>​<code>0020</code>​</td>
   <td>空格的权重被定义为低优先级（直接使用 Unicode 码点 U+0020）</td>
  </tr>
  <tr>
   <td>​<code>utf8mb4_0900_ai_ci</code>​</td>
   <td>​<code>0209</code>​</td>
   <td>空格的权重经过算法生成（符合 Unicode 9.0 标准，更精准）</td>
  </tr>
 </tbody>
</table>
<p><strong>1. 两个环境<code>support_language = ''</code>不一致的原因</strong>
 <br>
 ​<code>utf8mb4_general_ci</code> 在比较时<strong>会自动忽略尾随空格</strong>（非精确比较）。如果比较 <code>'a '</code> 和 <code>'a'</code>，也会返回 <code>true</code>。<code>utf8mb4_0900_ai_ci</code>严格遵循 Unicode 标准，权重的生成和比较逻辑一致，空格 <code>' '</code> 的权重（<code>0209</code>）与空字符串 <code>''</code>（权重为空）明确不同。</p>
<p><strong>2. 同一环境中，空字段与空格直接比较不一致的原因</strong>
 <br>
 那么对于<code>support_language = ''</code>为<code>true</code>的测试环境，<code>' ' = ''</code>为<code>false</code>，这又是为什么。问题出在字段取值和字面量比较上。</p>
<ul>
 <li><strong>字段比较</strong>：当比较字段值（即从表中读取）时，MySQL 会在 collation 的作用下自动去除尾随空格 —— 因此字段值中<code>' ' = ''</code>被视为相等。</li>
 <li><strong>字面量比较</strong>：写在 SQL 里的两个字符串字面量，字面量比较不走字段比较路径，在实际比较时不会进行这种“尾随空格忽略”处理，因此直接比较<code>' ' = ''</code>返回了<code>false</code>。</li>
</ul>
<h2 id="参考阅读">参考阅读</h2>
<ul>
 <li><a href="https://dev.mysql.com/doc/refman/8.4/en/charset-unicode-sets.html">MySQL :: MySQL 8.4 Reference Manual :: 12.10.1 Unicode Character Sets</a></li>
 <li><a href="https://stackoverflow.com/questions/34782594/mysql-space-equals-empty-string">mysql, space equals empty string - Stack Overflow</a></li>
</ul>]]></description><guid isPermaLink="false">/archives/9ucPekHi</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fqiniucloud.tsio.top%2Fblogfile%2F%25E9%259A%258F%25E6%2589%258B%25E8%25AE%25B0-lomgdads.png&amp;size=m" type="image/jpeg" length="0"/><category>随手记</category><category>技术博客</category><pubDate>Sat, 9 Aug 2025 04:01:00 GMT</pubDate></item><item><title><![CDATA[今天的晚风有了秋天的味道]]></title><link>https://blog.tsio.top/archives/aAN9pmrc</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E4%BB%8A%E5%A4%A9%E7%9A%84%E6%99%9A%E9%A3%8E%E6%9C%89%E4%BA%86%E7%A7%8B%E5%A4%A9%E7%9A%84%E5%91%B3%E9%81%93&amp;url=/archives/aAN9pmrc" width="1" height="1" alt="" style="opacity:0;">
<p style="">白天的气温还是酷暑难耐，通勤的路上还是汗如雨下，让人不得不怀疑这是个和秋天毫无关系的立秋。</p>
<p style=""></p>
<p style="">搞美术的同志们是动作最快的，他们立秋海报里的意象早就不知道秋到十月还是十一月去了。眼睛从手机屏幕上的黄叶枯枝移开，抬头却是一番割裂的景象，大脑接受到的信息认为这个节气还是更适合叫做“立夏”。</p>
<p style=""></p>
<p style="">要是有一个关于秋天的大辩论，那我一定是“我言秋日胜春朝”的那一派人。我热爱秋天，因为她清爽的天气和优雅的审美。</p>
<p style=""></p>
<p style="">由此我是盛爱东北的。</p>
<p style=""></p>
<p style="">东北的秋在八月底即可初见端倪：气温逐渐降低，秋高气爽的天占了大部分时光，树叶由墨绿开始变黄，世界的颜色开始变得层次分明。这样的进程要持续到十月底，黄叶枯萎，红叶掉落……如果你运气足够好的话，你应该可以看到最令人惊喜的景象——覆盖在黄叶上的一层薄薄的初雪！那种雪是极为软绵的，和冬日里的景象大不相同，你若是见了，一定会感叹：这就该是秋天的雪。</p>
<p style=""></p>
<p style="">北京的秋也是常被人盛赞的，去年在秋天的尾巴进了京，没来得及逛，秋天便结束了。这让我一边遗憾，一边期待今年的秋天。去见见人们念叨的京城秋景。不过相对于东北的秋，她晚得实在令人抓耳挠腮。</p>
<p style=""></p>
<p style="">在我的故乡四川，秋景并没有北方这般层次分明，但也有让我难以忘怀的锚点——桂花。在我看来，对于四季并不分明的地方，没有什么比桂花更能代表秋天了。桂花的清香总是伴随着凉爽的空气一起浸入人们的耳鼻，从此刻起不再能同夏季的自己共鸣，再不知道什么是炎热。只念着赏花、摘花、晒花、泡茶、桂花糕……</p>
<p style=""></p>
<p style="">这就不得不多嘴提一句妈妈制作的桂花茶，我长久找不到一个合适的词汇形容它的香气，干香的桂花香就像封存了一整个秋天，让你在任何时候只需要一杯热水，三两分钟就可以感受到秋天的味道。这种味道不像桂花香薰那般刺鼻，却是真正淡雅醇香的，独属于秋天的味道。</p>
<p style=""></p>
<p style="">今夜是入夏来我第一次在没有降雨的夜晚感受到凉风，所以激动的有些话多了，一写到睡觉的时候了，剩下的词让我们留着形容秋天吧！</p>
<p style=""></p>
<p style=""><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E7%25AB%258B%25E7%25A7%258B%25E7%259A%2584%25E6%259C%2588.jpg&amp;size=m" alt="立秋的月.jpg" width="100%" height="100%" style="display: inline-block"></p>]]></description><guid isPermaLink="false">/archives/aAN9pmrc</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fqiniucloud.tsio.top%2Fblogfile%2F%25E6%259D%2582%25E8%25AF%259D%25E9%2597%25B2%25E8%25B0%2588-qbwdkqvc.png&amp;size=m" type="image/jpeg" length="0"/><category>文学创作</category><pubDate>Thu, 7 Aug 2025 16:10:00 GMT</pubDate></item><item><title><![CDATA[博客数据库从 MySQL 迁移至 PostgreSQL 后资源占用变化报告]]></title><link>https://blog.tsio.top/archives/fPq21s5l</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E5%8D%9A%E5%AE%A2%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BB%8E%20MySQL%20%E8%BF%81%E7%A7%BB%E8%87%B3%20PostgreSQL%20%E5%90%8E%E8%B5%84%E6%BA%90%E5%8D%A0%E7%94%A8%E5%8F%98%E5%8C%96%E6%8A%A5%E5%91%8A&amp;url=/archives/fPq21s5l" width="1" height="1" alt="" style="opacity:0;">
<p><strong>先说结论</strong>：在资源受限的低配云服务器场景下，更推荐使用PostgreSQL作为博客数据库。</p>
<h2 id="背景">背景</h2>
<ul>
 <li>云服务器：腾讯云2核2G 轻量云服务器</li>
 <li>博客服务：Halo 2.20.13</li>
 <li>原数据库版本：MySQL 8.1.0</li>
 <li>目标数据库版本： PostgreSQL 17.5</li>
</ul>
<p>迁移原因：在该设备上运行的高内存占用服务只有两个，就是Halo博客和MySQL服务器，这俩的占内存用相对其它服务是断层的。Halo的日常占用在468MiB左右，MySQL也接近500MiB，让云服务器本就捉襟见肘的内存日常占用率都在89%左右，在进行特殊操作时更是达到95%上下。</p>
<p>过去两年间时常因为配置调整、应用变更等场景导致内存爆炸，然后死机。</p>
<h2 id="变更内容">变更内容</h2>
<ul>
 <li>新安装Halo框架及PostgreSQL，配置数据库连接</li>
 <li>初始化应用并恢复原博客内容备份</li>
 <li>更新博客内主题、插件等以匹配框架版本</li>
</ul>
<h2 id="变更效果">变更效果</h2>
<p>迁移后系统运行流畅，内存使用率由原先的近 95% 降至约 71%，整体下降 约 25%，并保持稳定。
 <br>
 PostgreSQL日常运行占用内存在80MiB左右，远低于MySQL，大幅降低内存崩溃风险。</p>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-WnDx.png&amp;size=m" alt="内存变化"></p>
<p>PostgreSQL 的内存使用策略更加保守，其 buffer 管理和连接池策略对低内存场景更为友好，相比 MySQL 默认的多线程结构和缓存策略更适合轻量级负载。更多SQL理论数据推荐参考其他博主的文章，放在参考阅读中了。</p>
<h2 id="参考阅读">参考阅读</h2>
<ul>
 <li><a href="https://www.cnblogs.com/blog5277/p/10658426.html">[评测]低配环境下，PostgresQL和Mysql读写性能简单对比（欢迎大家提出Mysql优化意见）- 曲高终和寡 - 博客园</a></li>
 <li><a href="https://cloud.tencent.com/developer/article/2423224">为什么高性能场景选用 PostgresSQL 而不是 MySQL？ - 腾讯云开发者社区 - 腾讯云</a></li>
</ul>]]></description><guid isPermaLink="false">/archives/fPq21s5l</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fqiniucloud.tsio.top%2Fblogfile%2F%25E5%258D%259A%25E5%25AE%25A2%25E7%25BB%25B4%25E6%258A%25A4-otdzgifi.png&amp;size=m" type="image/jpeg" length="0"/><category>技术博客</category><pubDate>Mon, 4 Aug 2025 08:18:00 GMT</pubDate></item><item><title><![CDATA[【博客维护】博客迁移与可能的访问异常问题说明]]></title><link>https://blog.tsio.top/archives/MKsf2DUp</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E5%8D%9A%E5%AE%A2%E7%BB%B4%E6%8A%A4%E3%80%91%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E4%B8%8E%E5%8F%AF%E8%83%BD%E7%9A%84%E8%AE%BF%E9%97%AE%E5%BC%82%E5%B8%B8%E9%97%AE%E9%A2%98%E8%AF%B4%E6%98%8E&amp;url=/archives/MKsf2DUp" width="1" height="1" alt="" style="opacity:0;">
<p style="">各位访问者及博主朋友们：</p>
<p style="text-indent: 2em">大家好！</p>
<p style="text-indent: 2em">目前博客主站数据库引擎从MySQL迁移到PostgreSQL，性能表现和相关情况还在监控中。</p>
<p style="text-indent: 2em">主站迁移后，部分防护规则与访问策略尚未全面部署。如您在浏览过程中遇到资源访问异常，欢迎通过评论或邮件反馈，我将尽快处理。</p>
<p style="text-indent: 2em">由于个人学习和工作的节奏有所调整，站群服务的资源规划与维护方向也在重新评估中。接下来博客平台可能会进行进一步的调整与迁移。详细计划正在参考各位博主的优秀方案进行制定和完善，欢迎提出您宝贵的建议。</p>
<p style="text-indent: 2em">在未来一段时间内（可能长达一个季度），博客整体流量可能有所下降。在维护过程中，如遇资源停运或配置更新，可能会短暂出现 502 错误等情况，敬请谅解。</p>
<p style="text-indent: 2em">谢谢。</p>
<p style="text-align: right">2025年7月28日</p>
<p style="text-align: right">光溯星河</p>]]></description><guid isPermaLink="false">/archives/MKsf2DUp</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fqiniucloud.tsio.top%2Fblogfile%2F%25E5%258D%259A%25E5%25AE%25A2%25E7%25BB%25B4%25E6%258A%25A4-otdzgifi.png&amp;size=m" type="image/jpeg" length="0"/><category>技术博客</category><pubDate>Mon, 28 Jul 2025 12:45:00 GMT</pubDate></item><item><title><![CDATA[数据库拆分策略：分区与分库分表的实现与应用场景对比]]></title><link>https://blog.tsio.top/archives/5Cf9TzBL</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8B%86%E5%88%86%E7%AD%96%E7%95%A5%EF%BC%9A%E5%88%86%E5%8C%BA%E4%B8%8E%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%B8%8E%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%E5%AF%B9%E6%AF%94&amp;url=/archives/5Cf9TzBL" width="1" height="1" alt="" style="opacity:0;">
<h1 id="结论先行">结论先行</h1>
<p><strong>分库分表</strong>：是一个<strong>应用架构级别的概念</strong>，意味着应用程序层面根据某些规则（如业务需求、数据量、负载均衡等）将数据水平拆分到不同的数据库或表中。分库分表的主要目的是解决数据量过大、性能瓶颈、系统扩展等问题。</p>
<ul>
 <li>
  <p><strong>分库</strong>：将一个大数据库拆分成多个独立的数据库实例，通常基于某些业务维度（如用户 ID、地理区域等）进行拆分。</p>
 </li>
 <li>
  <p><strong>分表</strong>：将某个表的数据拆分成多个子表，可以是水平拆分（按行）或垂直拆分（按列）。每个子表可以独立存储数据，并且通常也会放在同一个数据库实例中。</p>
 </li>
</ul>
<p>这些操作通常在应用程序层进行管理和路由。应用需要通过代码逻辑来决定数据的存储位置和访问方式，可能通过中间件或特定的路由算法来实现分库分表。</p>
<p><strong>分区</strong>：是<strong>数据库层级提供的功能</strong>，是数据库管理系统（DBMS）的一项内建特性，用来将表的数据物理上分割为多个部分（分区）。这些分区是由数据库自动管理的，用户只需要定义分区规则（如范围分区、哈希分区等），数据库系统会负责将数据存储到相应的分区中。分区不需要额外的应用逻辑或路由，数据库会自动处理查询、插入和更新。</p>
<table>
 <thead>
  <tr>
   <th>特性</th>
   <th><strong>分区</strong></th>
   <th><strong>分库</strong></th>
   <th><strong>分表</strong></th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><strong>概念层级</strong></td>
   <td>数据库</td>
   <td>应用</td>
   <td>应用</td>
  </tr>
  <tr>
   <td><strong>数据存储方式</strong></td>
   <td>单个表分成多个分区（同一个数据库内）</td>
   <td>数据分布到多个数据库中</td>
   <td>单表水平拆分成多个子表（同一个数据库内）</td>
  </tr>
  <tr>
   <td><strong>应用层复杂性</strong></td>
   <td>较低，查询对应用透明</td>
   <td>较高，应用层需要处理路由逻辑</td>
   <td>较高，应用层需要处理路由逻辑</td>
  </tr>
  <tr>
   <td><strong>查询复杂性</strong></td>
   <td>查询针对特定分区进行优化，性能较好</td>
   <td>跨库查询复杂，性能较差</td>
   <td>跨表查询复杂，性能较差</td>
  </tr>
  <tr>
   <td><strong>事务管理</strong></td>
   <td>支持，适合单个数据库内的事务管理</td>
   <td>分布式事务较为复杂，需要外部框架支持</td>
   <td>跨表事务复杂，可能需要分布式事务</td>
  </tr>
  <tr>
   <td><strong>适用场景</strong></td>
   <td>数据量大，范围查询为主，单库内优化</td>
   <td>大规模数据，需横向扩展，跨多个数据库</td>
   <td>数据量大，单库内水平扩展，适合高并发</td>
  </tr>
 </tbody>
</table>
<p><strong>区别简述</strong>：在搜索中可以发现，“分库分表”通常作为一个整体概念被广泛使用，在面对高并发、海量数据的场景时，它代表的是宏观上的、架构层面的数据库拆分策略。而相对应用较少的“分区”是一种单库单表内部的数据优化手段。</p>
<h1 id="分区partitioning">分区（Partitioning）</h1>
<p><em>常见数据库引擎都支持分区方式，本文描述以MySQL InnoDB为主。</em></p>
<blockquote>
 <p>MySQL数据库在5.1版本时添加了对分区的支持.MySQL数据库支持的分区类型为<strong>水平分区</strong>（指将同一个表中不同行的记录分配到不同的物理文件中）。</p>
</blockquote>
<p>分区是指将一个大的数据表按某一规则分成多个物理或逻辑部分，每个部分被称为分区。MySQL分区可将同一表中不同行的记录分配到不同的物理文件（.ibd）中，从而解决大表的查询索引问题。</p>
<p>关于表分区后的性能表现和应用场景实际情况辨析推荐参看参考阅读<a href="https://juejin.cn/post/6844904083443171335">1</a>和<a href="https://www.cnblogs.com/mzhaox/p/11201715.html">2</a>.</p>
<h2 id="特点">特点</h2>
<ul>
 <li>
  <p><strong>单个数据库</strong>：不涉及多个数据库的操作可以使用分区，分区表仍然属于一个数据库，表的所有分区都在同一个数据库中</p>
 </li>
 <li>
  <p><strong>同一个表</strong>：分区表的各个分区虽然存储在物理上是分开的，但它们仍然是同一个表，使用相同的表结构和索引</p>
 </li>
 <li>
  <p><strong>透明性</strong>：查询操作对应用程序透明，查询时 MySQL 会根据分区键自动选择需要扫描的分区，<strong>避免全表扫描</strong></p>
 </li>
</ul>
<h2 id="分区类型">分区类型</h2>
<p>可以选择按某个字段（如 <code>date</code>、<code>id</code> 等）进行分区。</p>
<p>MySQL水平分区支持的分区类型包括：范围、哈希、键值、列表和复合模式，这里只详细写出范围、列表和哈希三种分区，更多分区类型参看参考阅读链接或自行搜索。</p>
<h3 id="范围range">范围（RANGE）</h3>
<p>根据某个列（如日期或整数）的取值范围，将数据划分到不同的分区。</p>
<p>在 RANGE 分区中，每个分区通过 <code>VALUES LESS THAN (value)</code> 明确设定上限，若插入值未落入任何范围且未定义 <code>MAXVALUE</code> 分区，则会因“无分区可归”而插入失败（<code>ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition</code>），否则将自动归入包含 <code>MAXVALUE</code> 的分区中。</p>
<ul>
 <li>
  <p><strong>MAXVALUE</strong>：用于捕获超过所有定义范围的数据，是默认的“终结分区”</p>
 </li>
 <li>
  <p>范围区间需<strong>连续且不能重叠</strong>，每条记录只能进入一个分区</p>
 </li>
</ul>
<h3 id="列表list">列表（LIST）</h3>
<p>LIST 分区通过 <code>VALUES IN (value_list)</code> 精确指定哪些离散值归入每个分区，适用于分类固定、枚举清晰的字段，如地区或状态码。</p>
<p>标准 LIST 分区仅支持整数（包括 NULL）。LIST COLUMNS 分区支持<strong>非整数类型值</strong>，如字符串或日期类型，并允许按列进行精确匹配。</p>
<ul>
 <li>
  <p>每个值必须归属于某个分区，否则插入会失败</p>
 </li>
 <li>
  <p>分区定义应覆盖所有可能值，避免遗漏导致错误</p>
 </li>
</ul>
<h3 id="哈希hash">哈希（HASH）</h3>
<p>HASH 分区通过对指定表达式进行哈希运算，将数据均匀地自动分派到指定数量的分区中，适合无明显分区规则但需平均分布的场景。</p>
<p>使用语法 <code>PARTITION BY HASH(expr)</code>，其中 <code>expr</code> 是返回整数的表达式，通常为整型字段或计算表达式。系统会根据 <code>expr</code> 的哈希值自动将数据分布到预定义数量的分区中，不需要手动指定每个值所属分区。</p>
<ul>
 <li>
  <p>若表中含有唯一键或主键，<strong>用于分区的列必须包含在这些键中</strong>，否则会导致错误</p>
 </li>
 <li>
  <p>不支持分区裁剪（partition pruning）优化，因为分区选择是基于哈希，不可预测</p>
 </li>
 <li>
  <p>可使用表达式（如 <code>MOD(col, N)</code>）实现自定义分布逻辑</p>
 </li>
</ul>
<h2 id="demo-sql">Demo SQL</h2>
<ol>
 <li>
  <p>建立 LIST 分区表：</p>
  <pre><code class="language-SQL">CREATE TABLE random_name_library (
    id INT,
    language VARCHAR(10),
    used_count INT
    -- other columns...
) PARTITION BY LIST (language) (
    PARTITION en VALUES IN ('en'),
    PARTITION zh VALUES IN ('zh'),
    PARTITION other VALUES IN ('it', 'ja', 'hant')
);
</code></pre>
 </li>
 <li>
  <p>查看当前数据库是否启用了分区功能</p>
  <pre><code class="language-SQL">show global variables like '%partition%'
show plugins
</code></pre>
 </li>
</ol>
<h1 id="分库sharding--database-partitioning">分库（Sharding / Database Partitioning）</h1>
<p>与分区不同，分库分表是应用架构级别的概念，而非数据库提供的能力。分库分表的目的是将数据按策略分散到多个节点，通过负载均衡、故障转移等方式解决数据量过大、性能瓶颈、系统扩展等问题。</p>
<h2 id="特点-1">特点</h2>
<p>分库是将一个大型数据库按某些规则（如业务维度、数据范围、用户 ID 等）拆分成多个逻辑或物理独立的数据库。每个数据库存储数据的一个子集，共同组成完整的数据集。</p>
<ul>
 <li>
  <p><strong>水平扩展性强</strong>：通过增加数据库实例即可扩展系统能力</p>
 </li>
 <li>
  <p><strong>解耦和分担压力</strong>：将不同业务或数据维度分散存储，降低单库负载</p>
 </li>
 <li>
  <p><strong>常与分表结合使用</strong>：单表数据量大时，通常与分表一起使用形成“分库分表”</p>
 </li>
</ul>
<h2 id="分库类型">分库类型</h2>
<h3 id="水平分库sharding">水平分库（Sharding）</h3>
<p>将同一表的数据按照某种规则（如用户 ID、时间范围、哈希值等）拆分到多个数据库中。例如，用户表中的用户 ID 从 1～100 万放在 <code>db_user_0</code>，100 万～200 万放在 <code>db_user_1</code>。</p>
<ul>
 <li>
  <p><strong>优点</strong>：数据量均衡；支持横向扩展；负载均衡</p>
 </li>
 <li>
  <p><strong>缺点</strong>：跨库查询复杂；事务难以控制；需要全局 ID 生成方案（比如<a href="https://blog.tsio.top/archives/MduJz5Z2">UUID</a>）</p>
 </li>
</ul>
<h3 id="垂直分库vertical-partitioning">垂直分库（Vertical Partitioning）</h3>
<p>根据业务模块进行拆分，将不同业务的表分布在不同的数据库中。例如用户信息表放在 <code>db_user</code>，订单表放在 <code>db_order</code></p>
<ul>
 <li>
  <p><strong>优点</strong>：业务清晰、解耦；适合微服务架构。</p>
 </li>
 <li>
  <p><strong>缺点</strong>：存在跨库关联查询；拆分方案依赖业务理解</p>
 </li>
</ul>
<h2 id="支持分片的中间件">支持分片的中间件</h2>
<p>分库分表涉及应用层的路由、事务管理、查询合并等问题。在现代业务框架下，已经有不少来自各大互联网厂商的成熟方案，这些中间件面对不同场景提出了不同的解决方案。</p>
<p>常见的数据库中间件按其架构方式又可分为Proxy和Client两种模式。</p>
<ul>
 <li>
  <p><strong>代理模式（Proxy）</strong>：中间件通常充当数据库与应用程序之间的“中介”角色，应用程序的请求首先会通过中间件，然后由中间件将请求转发给对应的数据库实例。中间件充当数据库访问的代理，处理路由、负载均衡、分库分表等功能</p>
 </li>
 <li>
  <p><strong>客户端模式（Client）</strong>：中间件通常直接与应用程序集成，应用程序需要在客户端配置与数据库之间的访问规则。中间件处理分库分表、路由等操作，但应用程序需要了解和管理这些操作</p>
 </li>
</ul>
<p>市场上常见的开源中间件：</p>
<table>
 <thead>
  <tr>
   <th>中间件产品</th>
   <th>开发厂商</th>
   <th>代理模式</th>
   <th>特性</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><a href="https://shardingsphere.apache.org/index_zh.html">Apache ShardingSphere</a></td>
   <td>Apache</td>
   <td>Client(JDBC内嵌)/Proxy</td>
   <td>支持分库分表、读写分离、分布式事务、分片路由、分布式 ID 等</td>
  </tr>
  <tr>
   <td><a href="http://www.mycat.org.cn/">MyCAT</a></td>
   <td>MyCAT</td>
   <td>Proxy</td>
   <td>基于Cobar，支持分库分表、读写分离等</td>
  </tr>
  <tr>
   <td><a href="https://github.com/alibaba/cobar">Cobar</a></td>
   <td>阿里巴巴</td>
   <td>Proxy</td>
   <td>兼容MySQL协议、分片扩展、高可用集群</td>
  </tr>
  <tr>
   <td><a href="https://github.com/vitessio/vitess">Vitess</a></td>
   <td>Google (Youtube)</td>
   <td>Client</td>
   <td>支持 MySQL，具备强大的自动分片能力；适合超大规模系统</td>
  </tr>
 </tbody>
</table>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fraw.githubusercontent.com%2Falibaba%2Fcobar%2Fmaster%2Fdoc%2FCobar_architecture.png&amp;size=m" alt="阿里Cober框架">
 <br>
 阿里Cober框架</p>
<h1 id="分表table-sharding">分表（Table Sharding）</h1>
<p>同分库类似，分表也是一种数据分片策略，分表是将一个大的数据表拆分成多个小表，每个小表存储部分数据。每个子表有相同的结构和索引，但存储不同的数据。相对地，所有子表仍在同一个数据库中。</p>
<ul>
 <li>
  <p><strong>高可扩展性</strong>：可以通过增加更多的子表来水平扩展系统，而不需要改变现有的数据结构或业务逻辑</p>
 </li>
 <li>
  <p><strong>复杂的管理</strong>：分表带来的一大挑战是跨表查询、事务管理、数据迁移等方面的复杂性</p>
 </li>
</ul>
<h2 id="分表类型">分表类型</h2>
<h3 id="水平分表sharding"><strong>水平分表（Sharding）</strong></h3>
<p>水平分表是将一个表的行数据拆分到多个子表中，每个子表存储一部分数据。适用于数据量极大且需要根据某个字段（如时间、ID、地区等）进行拆分的场景。尤其是在大规模数据的处理和查询中，能够有效分担单表的负载。</p>
<ul>
 <li>
  <p><strong>按范围</strong>：如根据某个字段的值（如时间、ID 范围）拆分数据</p>
 </li>
 <li>
  <p><strong>按哈希</strong>：根据某个字段的哈希值，将数据均匀分配到不同的子表中</p>
 </li>
 <li>
  <p><strong>按业务</strong>：根据不同的业务维度拆分，如按地区、用户等进行分表</p>
 </li>
</ul>
<h3 id="垂直分表vertical-sharding"><strong>垂直分表（Vertical Sharding）</strong></h3>
<p><strong>定义</strong>：垂直分表是将一个表的列数据拆分到多个子表中，每个子表存储该表的一部分字段。适用于表的列数较多，但数据表的某些列访问频率较低的情况。通过将频繁访问的列单独分离，可以减少查询时的数据扫描量。</p>
<ul>
 <li>
  <p><strong>按功能</strong>：将用户基本信息存储在一个表，将用户的订单信息存储在另一个表</p>
 </li>
 <li>
  <p><strong>按访问频率</strong>：将热点数据存储在一个表，将冷数据存储在另一个表</p>
 </li>
</ul>
<h1 id="参考阅读">参考阅读</h1>
<ol>
 <li>
  <p><a href="https://juejin.cn/post/6844904083443171335">MySQL InnoDB存储引擎:分区表分区是一种表的设计模式，正确的分区可以极大地提升数据库的查询效率，完成更高质量的 - 掘金</a></p>
 </li>
 <li>
  <p><a href="https://www.cnblogs.com/mzhaox/p/11201715.html">深入解析MySQL分区(Partition)功能 - 龙福 - 博客园</a></p>
 </li>
 <li>
  <p><a href="https://dev.mysql.com/doc/refman/8.0/en/partitioning.html">Chapter 26 Partitioning - MySQL 8.0 Reference</a></p>
 </li>
 <li>
  <p><a href="https://cloud.tencent.com/developer/article/2420277">MySQL分区表：万字详解与实践指南 - 腾讯云开发者社区</a></p>
 </li>
 <li>
  <p><a href="https://zhuanlan.zhihu.com/p/342814592">MySQL的分区/分库/分表总结 - 知乎</a></p>
 </li>
 <li>
  <p><a href="https://cloud.tencent.com/developer/article/1819045">实战彻底搞清分库分表（垂直分库，垂直分表，水平分库，水平分表）- 腾讯云开发者社区</a></p>
 </li>
 <li>
  <p><a href="https://zhuanlan.zhihu.com/p/25271876132">分库分表详解（背景、概念及十二种方式）- 知乎</a></p>
 </li>
 <li>
  <p><a href="https://doc.ruoyi.vip/ruoyi-cloud/cloud/sharding.html">分库分表 | RuoYi</a></p>
 </li>
</ol>]]></description><guid isPermaLink="false">/archives/5Cf9TzBL</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%2588%2586%25E5%258C%25BA%25E5%2588%2586%25E5%25BA%2593%25E5%2588%2586%25E8%25A1%25A8.png&amp;size=m" type="image/jpeg" length="182829"/><category>技术博客</category><pubDate>Fri, 25 Jul 2025 10:07:00 GMT</pubDate></item><item><title><![CDATA[【实践手记】Git重写已提交代码历史信息]]></title><link>https://blog.tsio.top/archives/UN1iPZMG</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E5%AE%9E%E8%B7%B5%E6%89%8B%E8%AE%B0%E3%80%91Git%E9%87%8D%E5%86%99%E5%B7%B2%E6%8F%90%E4%BA%A4%E4%BB%A3%E7%A0%81%E5%8E%86%E5%8F%B2%E4%BF%A1%E6%81%AF&amp;url=/archives/UN1iPZMG" width="1" height="1" alt="" style="opacity:0;">
<p>需求背景：项目与常用git配置信息（全局）不一致，应在git仓库中进行非全局配置，但已有三四次提交推送到了远程仓库中。需要修改Git项目中已提交代码的作者信息，包括本地未推送的提交和已推送到远程仓库的历史记录。</p>
<hr>
<p><strong>⚠注意</strong></p>
<p>本文所描述的情形并非一般生产环境常用的方法，此方法对代码仓库存在危害，非特殊需求不要采取这样的操作。</p>
<ul>
 <li><strong>此操作会重写Git历史记录</strong>，所有提交的SHA值都会改变</li>
 <li><strong>需要强制推送到远程仓库</strong></li>
 <li>仅在<strong>小团队、个人项目</strong>或<strong>项目早期</strong>中使用</li>
 <li>如有其他协作者，应确保团队成员了解此变更</li>
 <li>准备回滚方案</li>
</ul>
<p>⚠Git官方文档的警告</p>
<blockquote>
 <p><em>git filter-branch</em> 存在大量隐患，可能会对预期的历史重写产生不明显的误差（而且由于其性能糟糕，你几乎没有时间去研究这些问题）。 这些安全和性能问题无法向后兼容修复，因此不建议使用。 请使用其他历史过滤工具，如 <a href="https://github.com/newren/git-filter-repo/">git filter-repo</a>。 如果您仍然需要使用 <em>git filter-branch</em>，请仔细阅读 <a href="https://git-scm.com/docs/git-filter-branch/zh_HANS-CN#SAFETY">安全性</a>（和 <a href="https://git-scm.com/docs/git-filter-branch/zh_HANS-CN#PERFORMANCE">性能</a>）以了解 filter-branch 的隐患，然后尽可能合理地避免其中列出的危险。</p>
</blockquote>
<hr>
<h2 id="操作步骤">操作步骤</h2>
<h3 id="1-检查当前配置">1. 检查当前配置</h3>
<pre><code class="language-bash"># 查看当前项目的git配置
git config --list --local

# 查看当前提交历史和作者信息
git log --pretty=format:"%h %an &lt;%ae&gt; %s" -5
</code></pre>
<h3 id="2-配置新的作者信息仅对当前项目">2. 配置新的作者信息（仅对当前项目）</h3>
<pre><code class="language-bash"># 设置新的用户名和邮箱（仅对当前项目生效）
git config user.name "新用户名"
git config user.email "新邮箱@example.com"

# 验证配置
git config --list --local | grep user
</code></pre>
<h3 id="3-修改历史记录中的作者信息">3. 修改历史记录中的作者信息</h3>
<pre><code class="language-bash"># 使用git filter-branch批量修改所有提交的作者信息
git filter-branch --env-filter '
OLD_EMAIL="旧邮箱@example.com"
CORRECT_NAME="新用户名"
CORRECT_EMAIL="新邮箱@example.com"
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
    export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags
</code></pre>
<h3 id="4-验证修改结果">4. 验证修改结果</h3>
<pre><code class="language-bash"># 检查修改后的提交记录
git log --pretty=format:"%h %an &lt;%ae&gt; %s" -5

# 查看最近几次提交
git log --oneline -3
</code></pre>
<h3 id="5-推送到远程仓库">5. 推送到远程仓库</h3>
<pre><code class="language-bash"># 使用强制推送更新远程仓库（建议使用--force-with-lease更安全）
git push --force-with-lease origin main

# 或者使用普通强制推送（风险更高）
git push --force origin main
</code></pre>
<h3 id="6-清理临时文件">6. 清理临时文件</h3>
<pre><code class="language-bash"># 删除git filter-branch产生的临时文件
rm -rf .git/refs/original/

# Windows PowerShell版本
Remove-Item -Recurse -Force .git/refs/original/ -ErrorAction SilentlyContinue
</code></pre>
<h2 id="变更验证">变更验证</h2>
<p>本地验证：</p>
<pre><code class="language-bash"># 查看提交历史
git log --oneline -10

# 查看作者信息
git log --pretty=format:"%h %an &lt;%ae&gt; %s" -10

# 检查当前配置
git config user.name
git config user.email
</code></pre>
<p>远程验证：检查远程仓库的作者信息是否正确更新</p>
<h2 id="其它方案">其它方案</h2>
<p>如果需求变更并没有上面描述的常见特殊，可以采用下面更安全的替代方案。</p>
<h3 id="仅修改最后一次提交">仅修改最后一次提交</h3>
<pre><code class="language-bash">bash git commit --amend --author="新用户名 &lt;新邮箱@example.com&gt;"
</code></pre>
<h3 id="使用git-filter-repo推荐">使用git filter-repo（推荐）</h3>
<p>现代替代方案，比filter-branch更安全：</p>
<pre><code class="language-bash"># 手动安装git filter-repo
pip install git-filter-repo

# 修改作者信息
git filter-repo --mailmap mailmap.txt
</code></pre>
<p>mailmap.txt文件格式：</p>
<pre><code>新用户名 &lt;新邮箱@example.com&gt; &lt;旧邮箱@example.com&gt;
</code></pre>
<h2 id="参考阅读">参考阅读</h2>
<ul>
 <li><a href="https://zhuanlan.zhihu.com/p/685073933">探索 Git 引用日志：利用 git reflog 恢复、理解和追溯 Git 操作历史 - 知乎</a></li>
 <li><a href="https://git-scm.com/docs/git-filter-branch/zh_HANS-CN">Git - git-filter-branch Documentation</a></li>
 <li><a href="https://github.com/newren/git-filter-repo/">newren/git-filter-repo: Quickly rewrite git repository history (filter-branch replacement)</a></li>
</ul>]]></description><guid isPermaLink="false">/archives/UN1iPZMG</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fgit%25E9%2587%258D%25E5%2586%2599%25E5%25B7%25B2%25E6%258F%2590%25E4%25BA%25A4%25E4%25BB%25A3%25E7%25A0%2581%25E4%25BF%25A1%25E6%2581%25AF-%25E5%258D%259A%25E5%25AE%25A2.png&amp;size=m" type="image/jpeg" length="117340"/><category>技术博客</category><pubDate>Fri, 4 Jul 2025 06:40:00 GMT</pubDate></item><item><title><![CDATA[图像工具箱服务"Markr" Beta上线（持续迭代中）]]></title><link>https://blog.tsio.top/archives/nSoRYIIY</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E5%9B%BE%E5%83%8F%E5%B7%A5%E5%85%B7%E7%AE%B1%E6%9C%8D%E5%8A%A1%22Markr%22%20Beta%E4%B8%8A%E7%BA%BF%EF%BC%88%E6%8C%81%E7%BB%AD%E8%BF%AD%E4%BB%A3%E4%B8%AD%EF%BC%89&amp;url=/archives/nSoRYIIY" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="markr%E6%9D%A5%E6%BA%90">Markr来源</h2>
<p style="">最初只是想写个脚本批量添加水印，研究了一下，参考了几个方案之后想法越来越多。</p>
<p style="">于是想要搓一个后端服务，基础API实现、鉴权都写好了，突然发现后端实现图像处理就是个很蠢的事情。</p>
<p style="">然后转向纯静态前端实现方案，构建快速、部署方便还安全。</p>
<p style="">唯一的缺点是不会前端技术栈。</p>
<p style="">这里就要感叹一下Cursor的强大了，半天这个项目就搓完了。</p>
<p style="">工程名称frameMark，应用起名<strong>Markr</strong>.</p>
<p style="">还是有很多不符合预期的地方，功能也还有缺失。这玩应整体需求量不大，按这个开发效率迭代几回就好了。</p>
<p style="">本文为发布文章，基础功能完善期间也会编辑更新几次日志。</p>
<p style="">（我其实还有一大票idea，大二时候就开始列，被我叫做“需求驱动的项目设计”，一开始就打算以ai为辅助整点活儿，没想到第一个试水的是这个项目，接着往下整吧。偏应用类的准备按这个经验接着整，后端和基架项目还是多手搓吧）</p>
<h2 style="" id="%E9%A1%B9%E7%9B%AE%E9%83%A8%E7%BD%B2">项目部署</h2>
<p style="">项目部署于github pages，配置访问链接：<a href="https://markr.tsio.top/">Markr - 图片工具箱</a></p>
<p style="text-align: center"><iframe src="https://markr.tsio.top/" width="100%" height="834px" frameborder="0" allowfullscreen="true" framespacing="0" style="display: inline-block"></iframe></p>
<h2 style="" id="%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97">更新日志</h2>
<h3 style="" id="beta-0.0.1-markr-%E7%89%B9%E6%80%A7%E8%AF%B4%E6%98%8E">Beta 0.0.1 Markr 特性说明</h3>
<ul>
 <li>
  <p style="">两大目标功能模块：边框水印、图片拼图</p>
 </li>
 <li>
  <p style="">设计语言：莫兰迪棕色 + 模糊卡片，略偏文艺，不扎眼</p>
 </li>
 <li>
  <p style="">纯静态设计：部署方便、应用安全</p>
 </li>
</ul>
<h3 style="" id="beta-0.0.2-%E8%BE%B9%E6%A1%86%E6%B0%B4%E5%8D%B0%E5%A4%A7%E5%B9%85%E4%BC%98%E5%8C%96">Beta 0.0.2 边框水印大幅优化</h3>
<ul>
 <li>
  <p style="">重构边框水印工具界面和展示信息</p>
 </li>
 <li>
  <p style="">优化图片上传和设置功能</p>
 </li>
 <li>
  <p style="">日期时间拆分字段</p>
 </li>
 <li>
  <p style="">重构“底边条幅”边框类型</p>
 </li>
 <li>
  <p style="">增加多种边框可选项和莫兰迪色系预设</p>
 </li>
 <li>
  <p style="">水印位置可通过九宫格选项自选</p>
 </li>
 <li>
  <p style="">支持自定义文件名</p>
 </li>
 <li>
  <p style="">增加快捷预设模板</p>
 </li>
 <li>
  <p style="">修复了一些bug</p>
 </li>
</ul>
<h3 style="" id="beta-0.0.3-%E8%BE%B9%E6%A1%86%E6%B0%B4%E5%8D%B0%E7%BB%86%E8%8A%82%E4%BC%98%E5%8C%96">Beta 0.0.3 边框水印细节优化</h3>
<ul>
 <li>
  <p style="">更新网站图标，生成自<a rel="nofollow" href="https://favicon.io/"><u>favicon.io</u></a></p>
 </li>
 <li>
  <p style="">优化字体选择器，精简字体，字体列表渲染为字体样式</p>
 </li>
 <li>
  <p style="">增加导出预览，预览为原图分辨率</p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/nSoRYIIY</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fmarkr%25E5%258F%2591%25E5%25B8%2583.png&amp;size=m" type="image/jpeg" length="115042"/><category>技术博客</category><pubDate>Wed, 2 Jul 2025 15:07:40 GMT</pubDate></item><item><title><![CDATA[【杂文·随笔】业余摄影：拍点什么好呢]]></title><link>https://blog.tsio.top/archives/jRlFJDYI</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%9D%82%E6%96%87%C2%B7%E9%9A%8F%E7%AC%94%E3%80%91%E4%B8%9A%E4%BD%99%E6%91%84%E5%BD%B1%EF%BC%9A%E6%8B%8D%E7%82%B9%E4%BB%80%E4%B9%88%E5%A5%BD%E5%91%A2&amp;url=/archives/jRlFJDYI" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>博客存储正在转型改造当中，查看本文摄影配图移步公众号文章：
  <br>
  <a href="https://mp.weixin.qq.com/s/sfPs2lUNl1jKFvpTDv937A?poc_token=HDs0YmijrxHQLcCnY0SBQOiHM0SOVfCDgY8dZNh2">【杂文·随笔】业余摄影：拍点什么好呢</a></p>
</blockquote>
<p>我们并非专业摄影师，也没有接受过科班训练。对于我们来说，摄影的第一需求并不是“创作”，而是“记录”——记录生活、记录当下、记录那些转瞬即逝却值得回味的瞬间。摄影之于我们，是一种业余爱好，它不该被专业理论裹挟，也无需照搬网络上的内容生产逻辑。与其把自己逼成“摄影内容创作者”，不如先回归初心：<strong>记录生活，感受快乐</strong>。</p>
<h2 id="01-拍到比拍好更重要">01 拍到比拍好更重要</h2>
<p>摄影的起点不是构图，不是用光，也不是器材，而是“按下快门”。</p>
<p>面对不同的题材、环境和情绪，拍法自然不同。但无论如何，先拍到，才有可能谈拍好。所以，器材重要吗？不那么重要。手机也行，老相机也好，关键是拿起它，走出家门，记录下你想留住的画面。不断按下快门，是在为生活留下回忆，也是在为自己积攒热情。拍得好不好，是后话；拍不拍，是关键。</p>
<p>摄影技术的进步、审美的提升，都是在“拍”的过程中自然积累的附随结果，而不是必须先学会的门槛。</p>
<h2 id="02-我们要拍什么">02 我们要拍什么</h2>
<p><strong>📍扫街</strong></p>
<p>把镜头对准我们生活的城市，不一定需要在某一个周末某一个假期专门规划扫街的路线，在上班路上、地铁站前、公司走廊……任何地方，我们拍下任何想拍的瞬间，它不一定有多么高的美学价值，但其纪实性对于我们的回忆来说是非常有意义的。当然，在某个空闲的假期，去走街串巷，提前规划，在人们都说好的地方走上一天，拍上一天也是怡情之举。</p>
<p><strong>🌄风光</strong></p>
<p>这是真正需要假期和时间的东西了，大家常说的大风光不仅需要壮美的景观，对摄影设备和后期能力的要求也非常苛刻，但这并不是我们所追求的。对于风光摄影的目标应该是对准我们旅行途中的自然景观，拍出对于一般游客来说不那么“游客”的“游客照”。通过摄影和组图的方式，去表达和强调我们在旅行途中的情绪以及收获。</p>
<p><strong>🧍‍♀️人像</strong></p>
<p>这可能是对于我的新手期最难逾越的门槛了，我想除了父母和朋友，我不敢把我的镜头对准任何其他人，当然，朋友还得是脾气好的朋友。此外，前辈们不断强调的引导和情绪价值也是一个并不简单的话题。综上所述，这个题材可以先放一放。</p>
<h2 id="03-直出还是大修">03 直出还是大修</h2>
<p>二者各有其妙用。我支持图片直出发圈，也非常乐意学习调色思路。</p>
<p><strong>↗️直出</strong></p>
<p>我认为日常扫街或者周末出游的风景照完全可以直出发表，原因很简单，没空。向你的朋友分享“最近去了哪”，“景观如何”，这样的话题是具有时效性的，如果在周一上班之前还要费大精力去修图，然后拖到周末都结束了再发表那是相当没必要的。若一张夕阳图得修图到夜里才发，那它对当天的意义就已经削弱了。</p>
<p><strong>🎨后期</strong></p>
<p>我认为后期图像的主要目的是学习别人的调色思路，从色彩组合中积累审美素养。不论是人文仿色的街拍照片，还是高度风格化的风光摄影，其本质已经脱离了“拍照”的范畴，是一种高度主观的摄影创作。其分享的内容不再是画面本身，而是一种情绪、一种感觉，是摄影师想要通过创作表达的生活图景或风光质感。</p>
<p>总的来说，我个人倾向于前期大于后期。这就回到了最初的讨论，对于摄影学习初期的我来说，按下快门是最重要的。那么接下来应该是拍好，而不是修好。当我们的学习深度逐渐形成，审美积累逐渐丰富，对于构图、色彩和摄影理论有了进一步的认识的情况下，再去尝试通过后期的裁切、调色对影像作品进行二次创作应该是更好的办法。</p>]]></description><guid isPermaLink="false">/archives/jRlFJDYI</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E6%2591%2584%25E5%25BD%25B1%25E9%259A%258F%25E7%25AC%2594-%25E5%258D%259A%25E5%25AE%25A2.png&amp;size=m" type="image/jpeg" length="29025"/><category>图一乐</category><pubDate>Mon, 30 Jun 2025 07:00:00 GMT</pubDate></item><item><title><![CDATA[【技术思辨】从空想走向实践：一场关于“架构设计”的认知碰撞]]></title><link>https://blog.tsio.top/archives/92HEwsk6</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E3%80%90%E6%8A%80%E6%9C%AF%E6%80%9D%E8%BE%A8%E3%80%91%E4%BB%8E%E7%A9%BA%E6%83%B3%E8%B5%B0%E5%90%91%E5%AE%9E%E8%B7%B5%EF%BC%9A%E4%B8%80%E5%9C%BA%E5%85%B3%E4%BA%8E%E2%80%9C%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E2%80%9D%E7%9A%84%E8%AE%A4%E7%9F%A5%E7%A2%B0%E6%92%9E&amp;url=/archives/92HEwsk6" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>俺原稿的表达挺菜的，感谢chatGPT的文段润色。</p>
</blockquote>
<p>大四以来的几段开发实习经历不断冲击着我对软件工程理论的原有认知。曾经，我坚定地认为只有构建出一个设计完美、结构严谨的系统架构，才能算作一名合格的软件工程师。但实际的开发环境却一次又一次打碎了这种“理想化”的幻想：现实项目的时间、资源、沟通成本远比书本中复杂得多，教条的架构推演往往难以落地。</p>
<p>在实习和正式工作前，我狂热崇拜“复杂架构”、“高内聚低耦合”、“完美的对象抽象”等理念。彼时，我以为启动一个项目就该预先搭好一整套框架体系，然后优雅地把现实需求抽象进一个个设计模式中，最终构建出一件“艺术品”般的架构作品。</p>
<p>这种思维导致我们在实际编码经验极少的情况下，动辄在项目初期做出夸张、繁复的系统设计，生怕漏掉哪一个被教材称之为“最佳实践”的原则。但现实教会我们，<strong>没有脱离上下文的“好架构”</strong>，任何技术选型与设计模式的选择都必须结合实际场景、团队能力和业务目标。</p>
<hr>
<p>我们学校的编程课程由C语言、C#、Java依次展开，自然形成一种线性演进的设计思维——似乎<strong>面向对象是函数式、过程式的“迭代”</strong>。我们被鼓励把任何现实事物抽象为对象，用类来组织一切结构，甚至在小项目里也力求贴合UML图的规范。</p>
<p>直到一段实习中，我与一位同学交流，他分享他们Leader的观点：“年轻人不要再用那些对象、类的老思想了，函数式才是未来的方向。”这让我意识到，不同技术领域对于设计范式的偏好有着天然分歧。而更深层的真相是：<strong>二者谁都不新鲜，谁也无法替代谁。</strong></p>
<p>无论是函数式还是面向对象，它们都是构建程序逻辑的工具——并不是理念高低之分，而是适配场景不同。对于以事件驱动和数据变更为主的前端开发，函数式会更简洁清晰；而在需要建模复杂业务实体的后端系统中，对象和继承更贴近问题域的思维方式。</p>
<p>如今的开发趋势中，我们几乎很少能看到“纯OO”或“纯FP”的项目。现代语言（如JavaScript、Python、Kotlin等）天生就支持混合编程范式：类结构用于数据组织，函数用于数据处理与流转。更多时候，我们使用<strong>面向对象来建模，用函数式处理业务逻辑与状态变更</strong>。</p>
<p>例如在前端项目中，我们常使用各类函数式手法，而底层状态管理可能仍依赖对象组织。而在微服务架构中，虽然服务之间用REST接口通信（过程式风格），每个服务内部却可能采用DDD（领域驱动设计）进行复杂建模。</p>
<p>开发从不是信仰之争，而是<strong>现实折衷</strong>：我们要根据<strong>系统复杂度、团队技术栈、业务目标与反馈周期</strong>不断调整工程方法，而非死守某种“主义”。</p>
<hr>
<p>我真正理解 <strong>MVA（最小可用架构）</strong> 的价值，并非在软件工程课上，而是在一个需求不断变化、资源极其有限的团队中，被产品经理不断催进度的过程中。</p>
<p>也许我们原本打算为系统构建一套“标准化插件框架”，试图覆盖未来所有可能出现的扩展场景。直到项目推进两周后才意识到，我们根本没那么多时间，也没有人力支撑这些“预设愿景”。于是，我们被迫采用<strong>降级思维</strong>：保留必要的抽象、砍掉无用的未来设想，转而构建一个能“活下来”、能“跑通主流程”的简单实现。</p>
<p>这一次经历彻底颠覆了我对“架构先行”的执念——<strong>好的架构是会不断进化的</strong>。</p>
<p>最小可用架构的价值在于它可以快速试错，收集反馈，其意义在于不被“系统工程的幻觉”困住，抓住核心价值点优先解决，再通过演进性重构支撑系统扩展。</p>
<hr>
<p>现实中的架构决策，往往不以技术“先进性”为驱动力，而是<strong>受限于更基础、更务实的资源约束</strong>——<strong>时间、人力、需求、反馈</strong>，这些才是工程系统真正的“第一推动力”。</p>
<p>在有限的开发周期内，我们无法为追求完美结构一再拖延交付；一套在实验室里“优雅至极”的架构，在现实中却可能因团队难以理解与维护而迅速劣化；而用户从不会因为我们用了DDD还是Hexagonal而买单，他们只关心能否快速体验、能否尽快反馈、能否实际解决问题。</p>
<p>涉世未深的我们很容易陷入“预设一切场景”的陷阱，为了所谓“未来可拓展”而架设了许多当下根本用不上的设计层，但最终，那些看似高级的结构因为无人理解、难以维护而成为了最沉重的技术债。</p>
<p>从“写得好”到“活得久”是一项重要的思维转变。架构不是预设出来的，而是在真实项目推进中，不断被需求和反馈锤炼出来的。在互联网基础设施建设完善的今天，我们常常以为那些大厂架构与中台设计都仿佛是某种自上而下、先验设计的成果，令人误以为伟大的系统都是一开始就被设计得井井有条。</p>
<p>我越来越清楚地意识到架构的“高级”不是因为用了多复杂的技术，而是能否在现实复杂性中长期演化、稳定扩展。</p>]]></description><guid isPermaLink="false">/archives/92HEwsk6</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E4%25BB%258E%25E7%25A9%25BA%25E6%2583%25B3%25E8%25B5%25B0%25E5%2590%2591%25E5%25AE%259E%25E8%25B7%25B5.png&amp;size=m" type="image/jpeg" length="160758"/><category>技术博客</category><pubDate>Tue, 17 Jun 2025 15:54:00 GMT</pubDate></item><item><title><![CDATA[关于成长，我有一些话要说]]></title><link>https://blog.tsio.top/archives/igdG7ooA</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E5%85%B3%E4%BA%8E%E6%88%90%E9%95%BF%EF%BC%8C%E6%88%91%E6%9C%89%E4%B8%80%E4%BA%9B%E8%AF%9D%E8%A6%81%E8%AF%B4&amp;url=/archives/igdG7ooA" width="1" height="1" alt="" style="opacity:0;">
<blockquote>
 <p>在未来相当长的时间中，成长将是最核心的关键词。也许你会说成长将贯穿我们的一生，但这意味着在未来五到十年甚至更长的时间中，我们将谦卑而主动地选择成长。</p>
</blockquote>
<p>显而易见的是，在离开校园后的时光中，我们面临的事物将是过去人生中从未见过的、未敢想象的。一切的遭遇不可预见、难以捉摸，限制这一想象的是我们的认知。而随着未知事物的遭遇和认知的提升，我们将逐渐优化、完善自己对世界的理解和行为模式，由过往经历的积累逐渐塑造完整的个人性格，这一过程被人们称之为<strong>成长</strong>。</p>
<p>由此我有许多关于成长的话题想要讲，这很好理解，我有多么期待未来的生活，就有多少疑惑要在成长中探索。这样强烈的需求来自我对未来生活的构建理论，在一份规划中，我将生活划分为经济财务、职业生涯、业余爱好、知识管理和社会关系五个部分。这五个部分并不能完全覆盖生活的方方面面，但是足以体现我的关注重点，并且可以围绕这五个部分逐渐拓宽我们对于世界的认知，不至于面对未来茫茫然而无从下手。与之相对的是毕业前的学生生活一切以学习为中心，然后发散构建自己对周围人事物的认知，这样的成长路径是单一的、且受到保护的。也就是说，毕业之前我们只需关注自己的成长，而无需考虑这之外的任何其他问题，但从离开校园的那一刻起，一切都将发生改变：成长必须由我们自己负责。</p>
<h1 id="为什么">为什么</h1>
<p>事物是不断变化发展的，从中学到大学，在学生生涯的大部分时间中我都感到自己的知识量非常枯竭，常常在思考中因理论知识的不足而痛苦，最后只能感叹一句“思而不学则殆”。而随着进程推向毕业，进入社会，经济、生活和学习等方方面面的事务以及对人生的规划将发生剧烈变化，我狭隘片面的认知面对这一剧变就更显得捉襟见肘了。</p>
<p>同时，我们清晰地认识到，当下的我们并非身处一个平常的时代，对于一个青年来说，这是极有吸引力的，但对于一个认知尚未健全的青年来说，这是极危险的。</p>
<p>因此，我们需要将最主要的精力放到学习和成长中来，为面向社会的认知塑造服务，以确在未来的时间里建立正常的独立生活秩序，减轻家庭压力，优化工作生活水平。在此基础上，还有一部分精力要为长远的个人成长服务，这一成长包括独立生活、职业技术、心理健康、综合素质、知识储备和社交能力等方面。随着对社会认知的不断积累，前者的精力要逐渐向后者倾斜，届时，个人成长将真正成为伴随我们一生的主题。</p>
<h1 id="路径">路径</h1>
<p>这和理想有关。</p>
<p>十八岁那年我说过，也许有一天，成熟的自己会回头批评今天的决定，告诉我们“理想没有意义，我们注定平庸”，但我还年轻，就算知道也绝不否认理想的意义，四年之后，我依然这样认为。你看，这就是没有经历过社会的毒打，是的，就是没有。也许一两年，也许三五年后，我就会回来批评自己的理想多么幼稚，那只能说明当下的理想脱离实际，脱离生活，而那时我想理想并不会消失，它依然存在，且同我们一起成长，慢慢脱离幻想，离生活更近。</p>
<p>成长就是向着理想前进，个人的成长就是向着理想的自己前进。</p>
<p>我希望在逐步提升个人认知的前提下，构建稳固的经济基础和健康的日常生活，在此之上拓展丰富的业余爱好和优良的社交关系。而以这一切为基础，我要复述我对崇高理想的追求：</p>
<ul>
 <li>待我学成时，实现经济独立，家庭幸福；</li>
 <li>待我小有成就，解决实际问题，为开源社区做出贡献；</li>
 <li>待我经验丰富时，我要让后来者的学习更加轻松，至少要让他们在满是抄袭教程的论坛中找到真理；</li>
 <li>待我知识充沛，技术专精时，我们将用技术推进生产力的发展，用科学寻求人类的未来，我们要改变这个世界。</li>
</ul>
<p>这段话写于2023年的春节，但落笔时想来已久，我相信历经岁月之后，我依然再次自信地面对你：这就是我的理想。</p>
<h1 id="杂话">杂话</h1>
<p>其实，很多和我们一样的人，并非没有理想，只是随着时间的消磨，理想逐渐蒙尘，不敢承认，不敢面对，逐渐否认，而后猛烈地批判、讥讽，可当他们看清年轻且充满理想的面庞时，也总会落泪。</p>
<p>由此，我认为理想和成长并非什么讲不得的、私密的、羞耻的话题。没有实现的理想和泯然众人的成长并不可笑，普通人之所以被成为普通人，是因为我们才是绝大多数。作为世界的大多数，热烈而普通的人生却成为了见不得光的羞耻，是生活赋予了我们这样的自卑？也许闻道有先后和切莫妄自菲薄这样的话，在今天更适合写给自己。</p>]]></description><guid isPermaLink="false">/archives/igdG7ooA</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E6%2588%2590%25E9%2595%25BF.png&amp;size=m" type="image/jpeg" length="10108"/><category>随笔散记</category><pubDate>Fri, 16 May 2025 07:13:00 GMT</pubDate></item><item><title><![CDATA[理解RESTful API和gRPC：原理、技术、风格及应用对比]]></title><link>https://blog.tsio.top/archives/dTlvmf0W</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=%E7%90%86%E8%A7%A3RESTful%20API%E5%92%8CgRPC%EF%BC%9A%E5%8E%9F%E7%90%86%E3%80%81%E6%8A%80%E6%9C%AF%E3%80%81%E9%A3%8E%E6%A0%BC%E5%8F%8A%E5%BA%94%E7%94%A8%E5%AF%B9%E6%AF%94&amp;url=/archives/dTlvmf0W" width="1" height="1" alt="" style="opacity:0;">
<h1 id="理解restful-api和grpc原理技术风格及应用对比">理解RESTful API和gRPC：原理、技术、风格及应用对比</h1>
<p>网络API的概念伴随着C/S和分布式程序设计的发展而出现，早期RPC协议可以追溯到1974年的RFC674，它尝试定义在网络中共享资源的通用方法，拉开了RFC的序幕。随着Web应用的发展，REST以更加灵活的架构风格出现，它以资源为核心，将操作映射到HTTP方法，通过URL定位资源，以极强的兼容性和易于实现的特性成为了Web API的实施标准。</p>
<p>侧重于状态转换的REST在处理复杂数据查询和前端需求频繁变化时表现出了明显的不足：REST接口通常是固定的，前端需要多个接口来获取不同的数据，这就可能导致多次请求和数据冗余。面对这一问题，Facebook在2015年推出了API查询语言GraphQL，允许客户端只请求所需的数据，从而优化数据检索并提高效率。</p>
<p>而gRPC由Google于2015年开发并开源，正式版本1.0发布于2016年8月，为了解决在微服务架构和分布式系统中服务间通信的性能问题，使用了Protocol Buffers作为序列化机制，基于HTTP/2支持多路复用，相对于RESTful API使用的JSON/XML格式和HTTP/1.1拥有更小的数据体积和更低的处理延迟。</p>
<p>网上有很多关于API风格的介绍和对比，出于近期的学习需求，本文着重关注RESTful API和gRPC在原理、技术、风格和应用场景等方面的实现和比较。</p>
<h2 id="restful-api">RESTful API</h2>
<h3 id="定义">定义</h3>
<p>REST全称Representational State Transfer，表述性状态转移。2000 年，<a href="https://roy.gbiv.com/">Roy T. Fielding</a> 在博士论文<a href="https://ics.uci.edu/~fielding/pubs/dissertation/top.htm">Architectural Styles and the Design of Network-based Software Architectures</a>中正式引入这一概念。</p>
<p>首先需要明确的是REST并没有提出新的技术，而是一种用于构建网络应用程序的软件架构风格，即<strong>RESTful是一种风格而不是一种标准</strong>。</p>
<p>在现代网络实践中，REST将网络中的一切事物都视为资源，每个资源都有唯一的标识符（通常是URL）。客户端通过 HTTP 请求方法（如 GET 用于获取资源、POST 用于创建资源、PUT 用于更新资源、DELETE 用于删除资源）对这些资源进行操作，服务器根据客户端的请求返回相应的资源表述，通常以 JSON、XML 等格式呈现。</p>
<p>（准确的说REST并不基于HTTP，它可以是任意的实现和URI，不过在我们的互联网中HTTP是其唯一的实例）</p>
<h3 id="核心规范">核心规范</h3>
<h4 id="1-资源">1. 资源</h4>
<ul>
 <li><strong>资源抽象</strong>：把所有事物抽象成资源，每个资源对应一个唯一的标识符，URI应当具有层级结构</li>
 <li><strong>资源命名</strong>：使用名词来表示资源而不是动词，例如使用<code>GET /system/users/1</code>​而非<code>GET /system/getUsers/1</code>​</li>
</ul>
<h4 id="2-接口">2. 接口</h4>
<ul>
 <li>
  <p><strong>操作表示</strong>：使用HTTP method表示操作，确保资源操作的表示和限制</p>
  <pre><code class="language-http">GET /articles/1 获取资源
POST /articles 创建资源
DELETE /articles/1 删除资源
</code></pre>
 </li>
 <li>
  <p><strong>请求结果</strong>：使用HTTP状态码表示请求结果</p>
 </li>
 <li>
  <p><strong>数据载体</strong>：使用JSON或XML表示数据，大多数时候是JSON</p>
 </li>
 <li>
  <p><strong>版本管理</strong>：应使用版本号来管理API以支持旧版API兼容性，例如<code>GET /v1/users/1</code>​</p>
 </li>
</ul>
<h4 id="3-分层">3. 分层</h4>
<ul>
 <li><strong>系统分层架构</strong>：RESTful API 可以采用分层的架构，每一层都有特定的职责</li>
 <li><strong>透明性</strong>：客户端无需关心其他系统层次是如何处理业务的
  <br>
  ‍</li>
</ul>
<h3 id="特性">特性</h3>
<ul>
 <li><strong>无状态</strong>：服务器不会保存客户端的状态信息，每个请求都要包含足够的信息以便服务器能独立处理该请求</li>
 <li><strong>可扩展</strong>：当需要添加新功能或资源时，只需按照统一的接口规范增加新的URI和对应的操作方法</li>
 <li><strong>可读性</strong>：通过使用直观的 URI 命名和标准的 HTTP 方法，开发人员可以很容易地理解每个请求的目的和作用</li>
</ul>
<h2 id="grpc">gRPC</h2>
<h3 id="定义-1">定义</h3>
<p>gRPC 是一个高性能、开源的远程过程调用（RPC）框架。2015 年，Google 将其内部长期使用的 RPC 框架 Stubby 进行了开源和改进，正式推出了 gRPC，由云原生计算基金会（CNCF）管理。</p>
<p>gRPC 基于 HTTP/2 协议，客户端和服务器可以在各种环境中运行并相互通信。客户端和服务器可以使用 gRPC 支持的任何语言编写，如 C++、Python、Java、C# 等。gRPC 通过 Protocol Buffers 进行数据的序列化和结构化，Protocol Buffers 是一种二进制格式，相比 JSON/XML 等序列化文本，具有更高的编码和解码效率，且序列化后的数据体积更小，传输速度更快，因此更加高效。</p>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fgrpc.org.cn%2Fimg%2Flanding-2.svg&amp;size=m" alt="gRPC概述" title="gRPC概述 - gRPC中文">​</p>
<h3 id="核心原理">核心原理</h3>
<h4 id="1-http2">1. HTTP/2</h4>
<ul>
 <li>gRPC 基于 HTTP/2 协议进行通信</li>
</ul>
<h4 id="2-服务定义与序列化">2. 服务定义与序列化</h4>
<ul>
 <li>
  <p>使用 Protocol Buffers（protobuf）来定义服务接口和消息类型。在.proto 文件中，开发者可以清晰地定义服务的方法、输入参数和输出结果</p>
  <pre><code class="language-protobuf">syntax = "proto3";

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string message = 1;
}
</code></pre>
 </li>
 <li>
  <p>Protocol Buffers 用于数据的序列化和反序列化，它将结构化的数据转换为二进制格式，在网络上进行传输</p>
 </li>
</ul>
<h4 id="3-远程过程调用">3. 远程过程调用</h4>
<ul>
 <li>客户端通过生成的 gRPC 客户端 stub 来调用服务器端的方法，就像调用本地方法一样。当客户端调用方法时，gRPC 框架会将参数序列化为二进制数据，通过 HTTP/2 连接发送到服务器</li>
 <li>服务器接收到请求后，进行反序列化，执行相应的方法，并将结果序列化后返回给客户端。客户端再将返回的结果反序列化，提供给应用程序使用</li>
</ul>
<h4 id="4-服务端与客户端流">4. 服务端与客户端流</h4>
<p>gRPC 支持多种交互模式，允许客户端和服务器之间进行双向的数据流传输：</p>
<ul>
 <li><strong>一元</strong>：客户端请求 - 服务端响应 模式</li>
 <li><strong>服务器流</strong>：客户端发起请求，服务器响应一个消息流，全部数据发送完毕后服务器再发送一条状态消息表示完成</li>
 <li><strong>客户端流</strong>：客户端向服务器发送一个消息流，并接受单个响应消息</li>
 <li><strong>双向流</strong>：客户端和服务器建立两个独立的流，他们可以任何顺序传输消息。客户端负责发起并结束双向流</li>
</ul>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fstatic001.geekbang.org%2Finfoq%2F15%2F155a193c6cbba97475ed4bb8c4304093.png&amp;size=m" alt="gRPC流类型" title="gRPC流类型 - InfoQ"></p>
<h4 id="5-拦截器与中间件">5. 拦截器与中间件</h4>
<ul>
 <li>gRPC 提供了拦截器机制，允许开发者在客户端和服务器端插入自定义的逻辑，例如日志记录、认证授权、数据校验等。拦截器可以在方法调用前后执行特定的操作，对请求和响应进行处理，从而实现通用的功能逻辑，而无需在每个具体的服务方法中重复编写代码。</li>
</ul>
<h3 id="特性-1">特性</h3>
<ul>
 <li><strong>高性能</strong>：HTTP/2 具有多路复用、头部压缩、二进制分帧等特性，能够在一个连接上同时处理多个请求和响应，提高了通信效率，减少了延迟，并且可以更好地利用网络带宽。同时，使用二进制格式的 Protocol Buffers 进行数据序列化，比 JSON、XML 等文本格式序列化和反序列化速度更快，数据体积更小</li>
 <li><strong>跨语言支持</strong>：Protocol Buffers定义具有语言无关性，支持多种编程语言，使得不同语言开发的客户端和服务器能够基于相同的接口定义进行通信</li>
 <li><strong>强类型定义</strong>：通过 Protocol Buffers 清晰地定义服务的方法、输入参数和输出结果，提供了强类型的接口定义</li>
 <li><strong>易于使用和集成</strong>：gRPC 提供了简洁的 API 和工具，使得开发者能够相对容易地定义服务和实现客户端与服务器端的代码</li>
</ul>
<p>‍</p>
<h2 id="技术对比">技术对比</h2>
<ul>
 <li>gRPC通常性能更高，适合高效通信和严格的接口定义场景，常用于软件客户端内部通信</li>
 <li>RESTful往往更易于理解和更广泛兼容（浏览器都支持HTTP），适合简单的HTTP通信和公共API，特别是与第三方程序进行交互的API</li>
</ul>
<table>
 <thead>
  <tr>
   <th>特性</th>
   <th>gRPC</th>
   <th>RESTful</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>文件格式</td>
   <td>Protocol Buffers（.proto）</td>
   <td>JSON、XML等</td>
  </tr>
  <tr>
   <td>性能</td>
   <td>高</td>
   <td>较低</td>
  </tr>
  <tr>
   <td>通信方式</td>
   <td>二进制</td>
   <td>文本</td>
  </tr>
  <tr>
   <td>支持的协议</td>
   <td>HTTP/2、gRPC自身协议</td>
   <td>HTTP/1.1、HTTP/2</td>
  </tr>
  <tr>
   <td>类型安全</td>
   <td>强</td>
   <td>弱</td>
  </tr>
  <tr>
   <td>语言支持</td>
   <td>多种编程语言（Java、C++、Go、Python等）</td>
   <td>所有支持HTTP的语言</td>
  </tr>
  <tr>
   <td>服务发现和负载均衡</td>
   <td>内置支持</td>
   <td>需要额外工具（如Consul、Eureka等）</td>
  </tr>
  <tr>
   <td>序列化速度</td>
   <td>快</td>
   <td>较慢</td>
  </tr>
  <tr>
   <td>流式支持</td>
   <td>内置支持服务端和客户端流式通信</td>
   <td>不直接支持，需要使用WebSocket等</td>
  </tr>
  <tr>
   <td>描述方式</td>
   <td>强类型定义的接口（.proto文件）</td>
   <td>自由结构化，不强制类型</td>
  </tr>
  <tr>
   <td>集成与兼容</td>
   <td>容易与Google生态系统中的其他工具集成</td>
   <td>广泛兼容和集成，可以与大多数系统和服务协作</td>
  </tr>
  <tr>
   <td>版本控制</td>
   <td>通过提升.proto文件版本号</td>
   <td>通过URL路径管理或使用头信息进行版本控制</td>
  </tr>
 </tbody>
</table>
<p>‍</p>
<h2 id="参考阅读">参考阅读</h2>
<ul>
 <li><a href="https://developer.aliyun.com/article/1463215">现代 API 的类型划分-阿里云开发者社区</a></li>
 <li><a href="https://zhuanlan.zhihu.com/p/664095171">API安全之《大话：API的前世今生》 - 知乎</a></li>
 <li><a href="https://aws.amazon.com/cn/compare/the-difference-between-grpc-and-rest/">gRPC 与 REST - 应用程序设计之间的区别 - AWS</a></li>
 <li><a href="https://www.infoq.cn/article/i71vnujcel09ebukak59">gRPC vs REST：两种API架构风格的对比_架构_André Santos_InfoQ精选文章</a></li>
 <li><a href="https://cloud.tencent.com/developer/article/1864288">RPC 发展史-腾讯云开发者社区-腾讯云</a></li>
 <li><a href="https://grpc.org.cn/docs/what-is-grpc/">什么是 gRPC？| gRPC 框架</a></li>
 <li><a href="https://www.runoob.com/w3cnote/restful-architecture.html">RESTful 架构详解 | 菜鸟教程</a></li>
 <li><a href="https://www.cnblogs.com/xiins/p/18723134">RESTful风格简介 - xiins - 博客园</a></li>
 <li><a href="https://www.ruanyifeng.com/blog/2011/09/restful.html">理解RESTful架构 - 阮一峰的网络日志</a></li>
</ul>
<p>‍</p>]]></description><guid isPermaLink="false">/archives/dTlvmf0W</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Frest%26grpc.png&amp;size=m" type="image/jpeg" length="77380"/><category>技术博客</category><pubDate>Thu, 10 Apr 2025 03:57:00 GMT</pubDate></item><item><title><![CDATA[Windows下使用Fiddler和mumu模拟器进行安卓app抓包实践分享]]></title><link>https://blog.tsio.top/archives/tAhjcmMj</link><description><![CDATA[<img src="https://blog.tsio.top/plugins/feed/assets/telemetry.gif?title=Windows%E4%B8%8B%E4%BD%BF%E7%94%A8Fiddler%E5%92%8Cmumu%E6%A8%A1%E6%8B%9F%E5%99%A8%E8%BF%9B%E8%A1%8C%E5%AE%89%E5%8D%93app%E6%8A%93%E5%8C%85%E5%AE%9E%E8%B7%B5%E5%88%86%E4%BA%AB&amp;url=/archives/tAhjcmMj" width="1" height="1" alt="" style="opacity:0;">
<p>出于开发需要，对移动端进行HTTP请求抓包，和客户端开发的同学对接新添加的API情况。网络上许多教程存在内容过时或配置不准确的情况，在进行配置时要注意自己的网络环境和软件版本。</p>
<p>平台环境如下，文中涉及到的资源都会标注，用到的资源链接也会统一放在参考阅读下：</p>
<ul>
 <li>操作系统：Windows 11 24H2</li>
 <li>Fiddler版本：v5.0.20204.45441 Ezrealik汉化版 from 吾爱破解</li>
 <li>mumu模拟器：V4.1.21 安卓12</li>
</ul>
<p>对移动应用进行抓包需要解决两个问题：</p>
<ul>
 <li>网络代理截取流量 - Fiddler Proxy</li>
 <li>高版本安卓不再支持用户安装证书 - mumu adb</li>
</ul>
<h2 id="操作步骤">操作步骤</h2>
<h3 id="1-下载工具">1. 下载工具</h3>
<ul>
 <li>MUMU模拟器：<a href="https://mumu.163.com/">MuMu模拟器官网_安卓12模拟器_网易手游模拟器</a></li>
 <li>Fiddler：
  <ul>
   <li>官网 <a href="https://www.telerik.com/fiddler">Web Debugging Proxy and Troubleshooting Tools | Fiddler</a></li>
   <li>仅供学习版 <a href="https://www.52pojie.cn/thread-1307586-1-1.html">抓包工具 Fiddler v5.0.20204.45441 简体中文版 - 吾爱破解 - 52pojie.cn</a></li>
  </ul></li>
 <li>adb：<a href="https://dl.google.com/android/repository/platform-tools-latest-windows.zip">https://dl.google.com/android/repository/platform-tools-latest-windows.zip</a></li>
</ul>
<h3 id="2-安装">2. 安装</h3>
<p>模拟器安装不用说，Fiddler学习版解压后放到app目录下开箱即用，ADB安装需要注意配置系统环境变量。</p>
<p>配置路径：<code>编辑系统环境变量 - 系统变量 - PATH - 新建</code>，然后将adb父级目录，即<code>platform-tools</code>放到环境变量中。</p>
<h3 id="3-配置mumu-root">3. 配置MUMU Root</h3>
<p>两个需要修改的地方，均在模拟器折叠菜单中，找到<strong>设置中心</strong>：</p>
<ol>
 <li>磁盘：勾选可写系统盘</li>
 <li>其他：开启Root权限</li>
</ol>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-20250327155130213.png&amp;size=m" alt="image-20250327155130213.png"></p>
<h3 id="4-配置fiddler并生成证书">4. 配置Fiddler并生成证书</h3>
<p>打开Fiddler，找到<code>工具（tools）-选项（options）- 连接（connections）</code>，其中有Fiddler监听端口，默认为8888。勾选下面的“<strong>允许远程计算机连接</strong>”选项。</p>
<p>接下来在<code>工具（tools）-选项（options）- HTTPS</code>中勾选解密HTTPS流量，根据指引生成证书，在“操作”中导出证书到路径下备用。
 <br>
 <img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-20250327155815551.png&amp;size=m" alt="image-20250327155815551.png"></p>
<h3 id="5-adb连接虚拟机拷贝证书">5. adb连接虚拟机拷贝证书</h3>
<p>同样在mumu折叠菜单中，设置中心下的问题诊断，我们可以在其中找到ADB调试端口<code>16384</code>。</p>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-20250327155400695.png&amp;size=m" alt="image-20250327155400695.png"></p>
<p>接下来进行adb连接并将证书推送到安卓虚拟机，目标目录为<code>/system/etc/security/cacerts/</code>。</p>
<pre><code class="language-powershell">❯ adb devices
❯ adb connect localhost:16384
❯ adb root # 回到mumu窗口，选择允许root调试
❯ adb push C:\YOUR_PATH\FiddlerRoot.cer /system/etc/security/cacerts/
</code></pre>
<h3 id="6-配置虚拟机网络代理">6. 配置虚拟机网络代理</h3>
<p>回到MUMU的安卓虚拟机，进入<code>设置 - 网络和互联网 - 互联网</code>，进入虚拟网络的详情页面。</p>
<ul>
 <li>查看网关地址</li>
 <li>配置手动代理</li>
</ul>
<p><img src="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-20250327160930233.png&amp;size=m" alt="image-20250327160930233.png"></p>
<p>接下来在虚拟机中打开app，回到Fiddler就可以看到网络请求了。</p>
<h2 id="参考阅读">参考阅读</h2>
<ul>
 <li>
  <p><a href="https://blog.csdn.net/m0_46607055/article/details/137638350">Burpsuite+MuMu模拟器12抓包_mumu模拟器burp-CSDN博客</a></p>
 </li>
 <li>
  <p><a href="https://blog.csdn.net/mcscm/article/details/135374802">mumu模拟器+fiddler抓APP包_mumu模拟器抓包-CSDN博客</a></p>
 </li>
</ul>]]></description><guid isPermaLink="false">/archives/tAhjcmMj</guid><dc:creator>timeStarry</dc:creator><enclosure url="https://blog.tsio.top/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fqiniucloud.tsio.top%2Fblogfile%2F%25E9%259A%258F%25E6%2589%258B%25E8%25AE%25B0-lomgdads.png&amp;size=m" type="image/jpeg" length="0"/><category>随手记</category><category>技术博客</category><pubDate>Thu, 27 Mar 2025 08:17:00 GMT</pubDate></item></channel></rss>